From a47a2e77d89065122d5d7df9387304e2dd0739e6 Mon Sep 17 00:00:00 2001 From: George Zhang Date: Mon, 4 Mar 2024 16:00:29 -0500 Subject: [PATCH] Support multiple targets on one section properly Apply transform before PropagateTargets to special-case the first target and prioritize its ID and name. This makes references to the other targets point to the last target rather than the section itself (which has the old ID). Changelog: add:: --- sphinx_better_subsection.py | 68 +++++++++++++++++++++++++++++-------- 1 file changed, 54 insertions(+), 14 deletions(-) diff --git a/sphinx_better_subsection.py b/sphinx_better_subsection.py index fb95267..0e6fb99 100644 --- a/sphinx_better_subsection.py +++ b/sphinx_better_subsection.py @@ -34,10 +34,10 @@ class PreferSectionTarget(Transform): .. code-block:: xml ... - - - -
+ + + +
... Transforming it gives: @@ -45,19 +45,19 @@ class PreferSectionTarget(Transform): .. code-block:: xml ... - - + + -
+
... Note that the other IDs are all preserved; only the order is modified. Nested subsections are also checked. """ - # Post processing priority from - # https://www.sphinx-doc.org/en/master/extdev/appapi.html?highlight=transform#sphinx.application.Sphinx.add_transform - default_priority = 700 + # Run before `docutils.transforms.references.PropagateTransform` which has + # priority 260. + default_priority = 255 def apply(self): """Docutils transform entry point""" @@ -79,14 +79,54 @@ def apply(self): # Filter away nodes that aren't targets if not isinstance(last, nodes.target): continue - # Internal hyperlink targets have refid (external ones have refuri) - if "refid" not in last: + # Filter away targets with content + if ( + isinstance(last.parent, nodes.TextElement) + or last.hasattr("refid") + or last.hasattr("refuri") + or last.hasattr("refname") + ): continue - refid = last["refid"] - assert refid in node["ids"] + + # Store ID and name + refname = last["names"][0] + refid = last["ids"][0] + + # Propagate the previous target + # Source from `PropagateTargets.apply` + node["ids"].extend(last["ids"]) + node["names"].extend(last["names"]) + # Set defaults for node.expect_referenced_by_name/id. + if not hasattr(node, "expect_referenced_by_name"): + node.expect_referenced_by_name = {} + if not hasattr(node, "expect_referenced_by_id"): + node.expect_referenced_by_id = {} + for id_ in last["ids"]: + node.expect_referenced_by_id[id_] = last + # Update IDs to node mapping. + self.document.ids[id_] = node + last["ids"] = [] + for name in last["names"]: + node.expect_referenced_by_name[name] = last + last["names"] = [] + # If there are any expect_referenced_by_name/id attributes in + # target set, copy them to node. + node.expect_referenced_by_name.update( + getattr(last, "expect_referenced_by_name", {})) + node.expect_referenced_by_id.update( + getattr(last, "expect_referenced_by_id", {})) + # Set refid to point to the first former ID of target which is now + # an ID of next_node. + last["refid"] = refid + self.document.note_refid(last) + # End source + # Prefer the target's ID node["ids"].remove(refid) node["ids"].insert(0, refid) + # Also prefer the target's name + node["names"].remove(refname) + node["names"].insert(0, refname) def setup(app): """Sphinx extension entry point"""