diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a2f3f00d..309e5b3f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -36,7 +36,7 @@ repos: - types-urllib3 - sphinx~=8.2 - markdown-it-py~=4.2 - - mdit-py-plugins~=0.6.0 + - mdit-py-plugins~=0.7.0 files: > (?x)^( myst_parser/.*py| diff --git a/CHANGELOG.md b/CHANGELOG.md index ff2d904f..eaa00cc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## Unreleased + +### ✨ New Features + +- ✨ Add `"section_ref"` syntax extension for section-sign references (e.g. `§1`, `§1.1`), which resolve to internal links to the correspondingly numbered heading in the document, see [](syntax/section-ref) by in + This extension requires `mdit-py-plugins >= 0.7`. + ## 5.1.0 - 2026-05-13 ### ✨ New Features diff --git a/docs/configuration.md b/docs/configuration.md index 6f4d7790..9e5cfa10 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -179,6 +179,9 @@ linkify replacements : Automatically convert some common typographic texts +section_ref +: Resolve section-sign references such as `§1.1` to internal links, [see here](syntax/section-ref) for details + smartquotes : Automatically convert standard quotations to their opening/closing variants diff --git a/docs/syntax/optional.md b/docs/syntax/optional.md index c8b9f24c..e885015b 100644 --- a/docs/syntax/optional.md +++ b/docs/syntax/optional.md @@ -40,6 +40,7 @@ myst_enable_extensions = [ "html_image", "linkify", "replacements", + "section_ref", "smartquotes", "strikethrough", "substitution", @@ -325,6 +326,50 @@ Key differences: - **`gfm_autolink`** only matches `www.`-prefixed URLs, `http(s)://`/`mailto:`/`xmpp:` URLs, and email addresses. It has no extra dependency and follows GFM URL boundary rules exactly. ::: +(syntax/section-ref)= +## Section references + +```{versionadded} 5.2.0 +``` + +Adding `"section_ref"` to `myst_enable_extensions` (in the {{ confpy }}) will recognise section-sign references such as `§1`, `§1.1` and `§2.3.4`, and turn each into an internal link to the correspondingly numbered heading in the current document. + +For example, with the following document: + +```md +# Title + +See §1 and §1.1, and also §2. + +## Section One + +### Sub One One + +## Section Two +``` + +`§1` links to *Section One*, `§1.1` to *Sub One One*, and `§2` to *Section Two*, each link also carrying the target heading's title as a hover tooltip. + +The numbering is **document-local** and purely **structural**, computed from the heading hierarchy (independent of any `toctree` `:numbered:` numbering): + +- If the document has a single top-level heading (the common `# Title` layout), its subsections are numbered `§1`, `§2`, … in order. +- Otherwise the top-level headings themselves are numbered `§1`, `§2`, …. +- Deeper levels are appended with dots, so the second subsection of the first section is `§1.2`, and so on. + +A `§` must be immediately followed by digits (e.g. `§1.2`); no spaces are allowed, and a trailing dot is not consumed (so `see §1.` references `§1`). + +References that appear **inside a heading or inside link text** are intentionally left as styled text rather than converted to links, since a link there would nest inside another link (a heading reference is also copied into navigation and table-of-contents entries). + +:::{note} +References resolve to positions **in the current document only**. +If a reference cannot be resolved to a numbered heading (for example `§9.9` in a document with fewer sections), the reference is left as plain, `section-ref`-styled text and a `myst.section_ref` warning is emitted. +This warning can be suppressed *via* [`suppress_warnings`](sphinx/config-options), e.g. `suppress_warnings = ["myst.section_ref"]`. + +In single-page docutils output, a document consisting solely of a `# Title` and one `## Section` promotes both to the document title and subtitle (standard docutils behaviour), leaving no numbered sections to reference. + +Cross-document section references may be supported in a future release. +::: + (syntax/substitutions)= ## Substitutions (with Jinja2) diff --git a/myst_parser/config/main.py b/myst_parser/config/main.py index 1e203fa6..d3b2e8d3 100644 --- a/myst_parser/config/main.py +++ b/myst_parser/config/main.py @@ -42,6 +42,7 @@ def check_extensions(inst: "MdParserConfig", field: dc.Field, value: Any) -> Non "html_image", "linkify", "replacements", + "section_ref", "smartquotes", "strikethrough", "substitution", diff --git a/myst_parser/mdit_to_docutils/base.py b/myst_parser/mdit_to_docutils/base.py index 8a29b5b1..3c76cca5 100644 --- a/myst_parser/mdit_to_docutils/base.py +++ b/myst_parser/mdit_to_docutils/base.py @@ -1276,6 +1276,13 @@ def render_span(self, token: SyntaxTreeNode) -> None: with self.current_node_context(node, append=True): self.render_children(token) + def render_section_ref(self, token: SyntaxTreeNode) -> None: + """Render a section reference (§1.1), for later resolution by ``ResolveSectionRefs``.""" + node = nodes.inline(token.content, token.content, classes=["section-ref"]) + node["section_numbers"] = token.meta["numbers"] + self.add_line_and_source_path(node, token) + self.current_node.append(node) + def render_front_matter(self, token: SyntaxTreeNode) -> None: """Pass document front matter data.""" position = token_line(token, default=0) diff --git a/myst_parser/mdit_to_docutils/transforms.py b/myst_parser/mdit_to_docutils/transforms.py index a7f9427d..627ee28f 100644 --- a/myst_parser/mdit_to_docutils/transforms.py +++ b/myst_parser/mdit_to_docutils/transforms.py @@ -365,3 +365,122 @@ def apply(self, **kwargs: t.Any) -> None: refnode += nodes.inline( "#" + target, "#" + target, classes=["std", "std-ref"] ) + + +def _child_sections(node: nodes.Element) -> list[nodes.section]: + """Return the direct child sections of an element, in document order.""" + return [child for child in node.children if isinstance(child, nodes.section)] + + +def _number_sections( + sections: list[nodes.section], + prefix: tuple[int, ...], + number_map: dict[tuple[int, ...], nodes.section], +) -> None: + """Recursively assign structural numbers to sections, populating ``number_map``. + + The i-th (1-based) section at a level is numbered ``prefix + (i,)``, and the + numbering recurses into each section's direct child sections. + """ + for i, section in enumerate(sections, start=1): + number = prefix + (i,) + number_map[number] = section + _number_sections(_child_sections(section), number, number_map) + + +def _leave_section_ref_inert(node: nodes.Element) -> bool: + """Whether a section-reference marker (see ``render_section_ref``) should be left as inert styled text. + + A marker is left untouched (no link, no warning) when it sits inside: + + - a ``reference`` or ``pending_xref`` — converting it would nest an ```` + inside another ````, which is invalid HTML; or + - a ``title`` — a reference here would be copied into navigation/toc entries + (sphinx toctree entry links, docutils contents entries), again nesting + ```` in those navs. + """ + parent = node.parent + while parent is not None: + if ( + isinstance(parent, nodes.reference | nodes.title) + or parent.tagname == "pending_xref" + ): + return True + parent = parent.parent + return False + + +class ResolveSectionRefs(Transform): + """Resolve ``§1.1`` section references to the target section's anchor. + + Runs at priority 878, before ``ResolveAnchorIds`` (879) and before sphinx's + ``DoctreeReadEvent`` (880), so that doctree-read consumers (``env.titles``, + the toctree collector) see the resolved references rather than the raw + markers. By this point any doctitle promotion (320) has happened and every + section id exists (docutils' ``PropagateTargets`` (260)/sphinx's ``SortIds`` + (261) and the later id transforms have all run). Numbering is + document-local and purely structural (independent of any ``:numbered:`` + toctree), so the same references resolve identically in docutils and sphinx. + """ + + default_priority = 878 # before ResolveAnchorIds (879)/DoctreeReadEvent (880) + + def apply(self, **kwargs: t.Any) -> None: + """Apply the transform.""" + # gather the section-reference markers emitted by ``render_section_ref``; + # these can only exist when the ``section_ref`` extension was enabled at + # parse time, so their presence self-gates the transform + markers = [ + node + for node in findall(self.document)(nodes.inline) + if "section_numbers" in node + ] + if not markers: + return + + # build a document-local, structural map of section numbers to sections + roots = _child_sections(self.document) + # with a single top-level section (the common ``# Title`` layout, which + # docutils doctitle promotion and sphinx local numbering both assume), + # number its child sections; otherwise number the top-level sections + top_sections = _child_sections(roots[0]) if len(roots) == 1 else roots + number_map: dict[tuple[int, ...], nodes.section] = {} + _number_sections(top_sections, (), number_map) + + for node in markers: + # a reference nested inside a link or heading would produce invalid + # nested anchors, so such markers are left as inert styled text + if _leave_section_ref_inert(node): + continue + content = node.astext() + number = tuple(node["section_numbers"]) + section = number_map.get(number) + if section is not None and section["ids"]: + ref = nodes.reference( + "", + "", + internal=True, + refid=section["ids"][0], + classes=["section-ref"], + ) + ref += node.children + # add the target's title as a hover tooltip (``title="..."`` in + # sphinx HTML); the section's first child is its ``title`` + title_node = next( + ( + child + for child in section.children + if isinstance(child, nodes.title) + ), + None, + ) + if title_node is not None: + ref["reftitle"] = clean_astext(title_node) + node.parent.replace(node, ref) + else: + create_warning( + self.document, + f"Section reference target not found: {content!r}", + MystWarnings.SECTION_REF, + line=node.line, + ) diff --git a/myst_parser/parsers/docutils_.py b/myst_parser/parsers/docutils_.py index 177a7f50..91ae74e3 100644 --- a/myst_parser/parsers/docutils_.py +++ b/myst_parser/parsers/docutils_.py @@ -28,6 +28,7 @@ CollectFootnotes, PrioritiseExplicitIds, ResolveAnchorIds, + ResolveSectionRefs, SortFootnotes, UnreferencedFootnotesDetector, ) @@ -260,6 +261,7 @@ def get_transforms(self): AddSlugIds, PrioritiseExplicitIds, ResolveAnchorIds, + ResolveSectionRefs, ] def parse(self, inputstring: str, document: nodes.document) -> None: diff --git a/myst_parser/parsers/mdit.py b/myst_parser/parsers/mdit.py index 2422837b..b248096b 100644 --- a/myst_parser/parsers/mdit.py +++ b/myst_parser/parsers/mdit.py @@ -20,6 +20,7 @@ from mdit_py_plugins.gfm_autolink import gfm_autolink_plugin from mdit_py_plugins.myst_blocks import myst_block_plugin from mdit_py_plugins.myst_role import myst_role_plugin +from mdit_py_plugins.section_ref import section_ref_plugin from mdit_py_plugins.substitution import substitution_plugin from mdit_py_plugins.wordcount import wordcount_plugin @@ -84,6 +85,8 @@ def create_md_parser( linkify_enabled = True if "gfm_autolink" in config.enable_extensions: md.use(gfm_autolink_plugin) + if "section_ref" in config.enable_extensions: + md.use(section_ref_plugin) if "strikethrough" in config.enable_extensions: md.enable("strikethrough") md.options["strikethrough_single_tilde"] = config.strikethrough_single_tilde diff --git a/myst_parser/parsers/sphinx_.py b/myst_parser/parsers/sphinx_.py index 60035284..b2a83e41 100644 --- a/myst_parser/parsers/sphinx_.py +++ b/myst_parser/parsers/sphinx_.py @@ -19,6 +19,7 @@ CollectFootnotes, PrioritiseExplicitIds, ResolveAnchorIds, + ResolveSectionRefs, SortFootnotes, ) from myst_parser.parsers.mdit import create_md_parser @@ -58,6 +59,7 @@ def get_transforms(self): AddSlugIds, PrioritiseExplicitIds, ResolveAnchorIds, + ResolveSectionRefs, ] def parse(self, inputstring: str, document: nodes.document) -> None: diff --git a/myst_parser/warnings_.py b/myst_parser/warnings_.py index 99b9072f..60edcb33 100644 --- a/myst_parser/warnings_.py +++ b/myst_parser/warnings_.py @@ -66,6 +66,8 @@ class MystWarnings(Enum): """Invalid attribute value.""" SUBSTITUTION = "substitution" """Substitution could not be resolved.""" + SECTION_REF = "section_ref" + """A section reference could not be resolved.""" def _is_suppressed_warning( diff --git a/pyproject.toml b/pyproject.toml index 8c0d5d79..33215b05 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,7 @@ dependencies = [ "docutils>=0.20,<0.24", "jinja2", # required for substitutions, but let sphinx choose version "markdown-it-py~=4.2", - "mdit-py-plugins~=0.6,>=0.6.1", + "mdit-py-plugins~=0.7", "pyyaml", "sphinx>=8,<10", ] @@ -89,7 +89,7 @@ mypy = [ "types-urllib3", "sphinx~=8.2", "markdown-it-py~=4.2", - "mdit-py-plugins~=0.6.0", + "mdit-py-plugins~=0.7.0", ] ruff = ["ruff==0.15.20"] diff --git a/tests/test_docutils.py b/tests/test_docutils.py index 1e17235c..1ba23506 100644 --- a/tests/test_docutils.py +++ b/tests/test_docutils.py @@ -211,6 +211,116 @@ def test_linkify_no_warning_when_available(): assert "[myst.linkify]" not in stream.getvalue() +def test_section_ref_resolution(): + """A ``§1`` reference resolves to an internal link to the numbered heading.""" + source = dedent( + """\ + # Title + + See §1, §1.1 and §2. + + ## Section One + + ### Sub One One + + ## Section Two + """ + ) + doctree = publish_doctree( + source=source, + parser=Parser(), + settings_overrides={"myst_enable_extensions": ["section_ref"]}, + ) + refs = list(doctree.findall(nodes.reference)) + assert [(ref.astext(), ref["refid"], ref["reftitle"]) for ref in refs] == [ + ("§1", "section-one", "Section One"), + ("§1.1", "sub-one-one", "Sub One One"), + ("§2", "section-two", "Section Two"), + ] + assert all("section-ref" in ref["classes"] and ref["internal"] for ref in refs) + + +def test_section_ref_unresolved_warning(): + """An unresolvable ``§`` reference emits a suppressible ``myst.section_ref`` warning.""" + source = "# Title\n\nSee §9.9.\n\n## Only Section\n" + stream = io.StringIO() + doctree = publish_doctree( + source=source, + parser=Parser(), + settings_overrides={ + "myst_enable_extensions": ["section_ref"], + "warning_stream": stream, + }, + ) + assert "[myst.section_ref]" in stream.getvalue() + # the reference is left in place as styled inline text + inlines = [ + node for node in doctree.findall(nodes.inline) if "section_numbers" in node + ] + assert [node.astext() for node in inlines] == ["§9.9"] + + # the warning is suppressible + stream = io.StringIO() + publish_doctree( + source=source, + parser=Parser(), + settings_overrides={ + "myst_enable_extensions": ["section_ref"], + "myst_suppress_warnings": ["myst.section_ref"], + "warning_stream": stream, + }, + ) + assert "[myst.section_ref]" not in stream.getvalue() + + +def test_section_ref_left_inert_in_link_and_heading(): + """A ``§`` reference inside link text or a heading stays inert styled text. + + Converting it would nest an ```` inside another ```` (link text) or + inside toc/contents entry links (heading), so no reference is created and no + warning is emitted for it, whether or not the number would resolve. + """ + source = dedent( + """\ + # Title + + ## Heading with §1 inside + + ## Other + + A link [see §1](https://example.com) and body §1. + """ + ) + stream = io.StringIO() + doctree = publish_doctree( + source=source, + parser=Parser(), + settings_overrides={ + "myst_enable_extensions": ["section_ref"], + "warning_stream": stream, + }, + ) + # no reference is ever nested inside another reference + for ref in doctree.findall(nodes.reference): + assert not list(ref.findall(nodes.reference, include_self=False)) + # the heading ref and the in-link ref remain inert inline markers, + # while the plain body ref resolves to the first section + inert = [ + node for node in doctree.findall(nodes.inline) if "section_numbers" in node + ] + assert [node.astext() for node in inert] == ["§1", "§1"] + resolved = [ + ref + for ref in doctree.findall(nodes.reference) + if "section-ref" in ref["classes"] + ] + assert [(r.astext(), r["refid"]) for r in resolved] == [ + ("§1", "heading-with-1-inside") + ] + # inert markers are skipped silently (no warning) + assert "[myst.section_ref]" not in stream.getvalue() + + def test_html_deep_nesting_warns(): """Deeply nested HTML degrades to raw output with a warning. diff --git a/tests/test_renderers/fixtures/docutil_section_refs.md b/tests/test_renderers/fixtures/docutil_section_refs.md new file mode 100644 index 00000000..bfa3c78a --- /dev/null +++ b/tests/test_renderers/fixtures/docutil_section_refs.md @@ -0,0 +1,178 @@ +[resolved] --myst-enable-extensions=section_ref +. +# Title + +Intro referencing §1, §1.1 and §2. + +## Section One + +### Sub One One + +## Section Two +. + + + Title + <paragraph> + Intro referencing + <reference classes="section-ref" internal="True" refid="section-one" reftitle="Section One"> + §1 + , + <reference classes="section-ref" internal="True" refid="sub-one-one" reftitle="Sub One One"> + §1.1 + and + <reference classes="section-ref" internal="True" refid="section-two" reftitle="Section Two"> + §2 + . + <section ids="section-one" names="section\ one"> + <title> + Section One + <section ids="sub-one-one" names="sub\ one\ one"> + <title> + Sub One One + <section ids="section-two" names="section\ two"> + <title> + Section Two +. + +[multiple-headings] --myst-enable-extensions=section_ref +. +See §1 and §2. + +# First + +# Second +. +<document source="<src>/index.md"> + <paragraph> + See + <reference classes="section-ref" internal="True" refid="first" reftitle="First"> + §1 + and + <reference classes="section-ref" internal="True" refid="second" reftitle="Second"> + §2 + . + <section ids="first" names="first"> + <title> + First + <section ids="second" names="second"> + <title> + Second +. + +[forward-reference] --myst-enable-extensions=section_ref +. +# Title + +## First + +Text in first, see §2. + +## Second + +Text in second. +. +<document ids="title" names="title" source="<src>/index.md" title="Title"> + <title> + Title + <section ids="first" names="first"> + <title> + First + <paragraph> + Text in first, see + <reference classes="section-ref" internal="True" refid="second" reftitle="Second"> + §2 + . + <section ids="second" names="second"> + <title> + Second + <paragraph> + Text in second. +. + +[unresolved] --myst-enable-extensions=section_ref +. +# Title + +See §9.9 which does not exist. + +## Only Section +. +<document ids="title" names="title" source="<src>/index.md" title="Title"> + <title> + Title + <paragraph> + See + <inline classes="section-ref" section_numbers="9 9"> + §9.9 + which does not exist. + <section ids="only-section" names="only\ section"> + <title> + Only Section + + +<src>/index.md:3: (WARNING/2) Section reference target not found: '§9.9' [myst.section_ref] +. + +[in-link-text] --myst-enable-extensions=section_ref +. +# Title + +## One + +## Two + +A link [see §1](https://example.com) leaves §1 inert, but body §2 resolves. +. +<document ids="title" names="title" source="<src>/index.md" title="Title"> + <title> + Title + <section ids="one" names="one"> + <title> + One + <section ids="two" names="two"> + <title> + Two + <paragraph> + A link + <reference refuri="https://example.com"> + see + <inline classes="section-ref" section_numbers="1"> + §1 + leaves + <reference classes="section-ref" internal="True" refid="one" reftitle="One"> + §1 + inert, but body + <reference classes="section-ref" internal="True" refid="two" reftitle="Two"> + §2 + resolves. +. + +[in-heading] --myst-enable-extensions=section_ref +. +# Title + +## Heading with §1 inside + +## Other + +Body §1 resolves to the first section. +. +<document ids="title" names="title" source="<src>/index.md" title="Title"> + <title> + Title + <section ids="heading-with-1-inside" names="heading\ with\ §1\ inside"> + <title> + Heading with + <inline classes="section-ref" section_numbers="1"> + §1 + inside + <section ids="other" names="other"> + <title> + Other + <paragraph> + Body + <reference classes="section-ref" internal="True" refid="heading-with-1-inside" reftitle="Heading with §1 inside"> + §1 + resolves to the first section. +. diff --git a/tests/test_renderers/test_fixtures_docutils.py b/tests/test_renderers/test_fixtures_docutils.py index 4e52bb6b..fbe9ab3b 100644 --- a/tests/test_renderers/test_fixtures_docutils.py +++ b/tests/test_renderers/test_fixtures_docutils.py @@ -122,6 +122,35 @@ def _apply_transforms(self): ) +@pytest.mark.param_file(FIXTURE_PATH / "docutil_section_refs.md") +def test_section_refs(file_params: ParamTestData, normalize_doctree_xml): + """Test that ``§1.1`` section references resolve, or give the correct warning. + + The description is parsed as a docutils commandline. + Transforms are applied, since resolution happens in ``ResolveSectionRefs``. + """ + settings = settings_from_cmdline(file_params.description) + report_stream = StringIO() + settings["warning_stream"] = report_stream + doctree = publish_doctree( + file_params.content, + source_path="<src>/index.md", + parser=Parser(), + settings_overrides=settings, + ) + # docutils >=0.23 also inserts info-level (severity 1) system messages + # into the doctree; they are still written to the report stream, + # which is what the fixtures capture, so drop them from the tree + if docutils_version >= (0, 23): + for msg in list(doctree.findall(nodes.system_message)): + if msg["level"] < 2: + msg.parent.remove(msg) + outcome = normalize_doctree_xml(doctree.pformat()) + if report_stream.getvalue().strip(): + outcome += "\n\n" + report_stream.getvalue().strip() + file_params.assert_expected(outcome, rstrip_lines=True) + + @pytest.mark.param_file(FIXTURE_PATH / "docutil_syntax_extensions.txt") def test_syntax_extensions(file_params: ParamTestData, normalize_doctree_xml): """The description is parsed as a docutils commandline""" diff --git a/tests/test_sphinx/sourcedirs/extended_syntaxes/conf.py b/tests/test_sphinx/sourcedirs/extended_syntaxes/conf.py index 8a13151e..fdf67605 100644 --- a/tests/test_sphinx/sourcedirs/extended_syntaxes/conf.py +++ b/tests/test_sphinx/sourcedirs/extended_syntaxes/conf.py @@ -14,6 +14,7 @@ "tasklist", "attrs_inline", "attrs_block", + "section_ref", ] myst_number_code_blocks = ["typescript"] myst_html_meta = { diff --git a/tests/test_sphinx/sourcedirs/extended_syntaxes/index.md b/tests/test_sphinx/sourcedirs/extended_syntaxes/index.md index fd757f41..5e404e53 100644 --- a/tests/test_sphinx/sourcedirs/extended_syntaxes/index.md +++ b/tests/test_sphinx/sourcedirs/extended_syntaxes/index.md @@ -78,3 +78,11 @@ Numbered code block: ```typescript type Result = "pass" | "fail" ``` + +## Section with §1 self-ref + +Body referencing §1 and §1.1. + +### Nested section + +## Second section diff --git a/tests/test_sphinx/test_sphinx_builds/test_extended_syntaxes.html b/tests/test_sphinx/test_sphinx_builds/test_extended_syntaxes.html index 962b72cc..efbf3061 100644 --- a/tests/test_sphinx/test_sphinx_builds/test_extended_syntaxes.html +++ b/tests/test_sphinx/test_sphinx_builds/test_extended_syntaxes.html @@ -190,6 +190,45 @@ <h1> </pre> </div> </div> + <section id="section-with-1-self-ref"> + <h2> + Section with + <span class="section-ref"> + §1 + </span> + self-ref + <a class="headerlink" href="#section-with-1-self-ref" title="Link to this heading"> + ¶ + </a> + </h2> + <p> + Body referencing + <a class="section-ref reference internal" href="#section-with-1-self-ref" title="Section with §1 self-ref"> + §1 + </a> + and + <a class="section-ref reference internal" href="#nested-section" title="Nested section"> + §1.1 + </a> + . + </p> + <section id="nested-section"> + <h3> + Nested section + <a class="headerlink" href="#nested-section" title="Link to this heading"> + ¶ + </a> + </h3> + </section> + </section> + <section id="second-section"> + <h2> + Second section + <a class="headerlink" href="#second-section" title="Link to this heading"> + ¶ + </a> + </h2> + </section> </section> </div> </div> diff --git a/tests/test_sphinx/test_sphinx_builds/test_extended_syntaxes.xml b/tests/test_sphinx/test_sphinx_builds/test_extended_syntaxes.xml index f00cb508..57f23fc6 100644 --- a/tests/test_sphinx/test_sphinx_builds/test_extended_syntaxes.xml +++ b/tests/test_sphinx/test_sphinx_builds/test_extended_syntaxes.xml @@ -110,3 +110,23 @@ Numbered code block: <literal_block language="typescript" linenos="True" xml:space="preserve"> type Result = "pass" | "fail" + <section ids="section-with-1-self-ref" names="section\ with\ §1\ self-ref"> + <title> + Section with + <inline classes="section-ref" section_numbers="1"> + §1 + self-ref + <paragraph> + Body referencing + <reference classes="section-ref" internal="True" refid="section-with-1-self-ref" reftitle="Section with §1 self-ref"> + §1 + and + <reference classes="section-ref" internal="True" refid="nested-section" reftitle="Nested section"> + §1.1 + . + <section ids="nested-section" names="nested\ section"> + <title> + Nested section + <section ids="second-section" names="second\ section"> + <title> + Second section diff --git a/tests/test_sphinx/test_sphinx_builds/test_extended_syntaxes_text.txt b/tests/test_sphinx/test_sphinx_builds/test_extended_syntaxes_text.txt index e35e2598..aa20aee6 100644 --- a/tests/test_sphinx/test_sphinx_builds/test_extended_syntaxes_text.txt +++ b/tests/test_sphinx/test_sphinx_builds/test_extended_syntaxes_text.txt @@ -61,3 +61,17 @@ linkify URL: www.example.com Numbered code block: type Result = "pass" | "fail" + + +Section with §1 self-ref +======================== + +Body referencing §1 and §1.1. + + +Nested section +-------------- + + +Second section +==============