Skip to content

Commit

Permalink
Support multiple targets on one section properly
Browse files Browse the repository at this point in the history
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::
  • Loading branch information
GeeTransit committed Mar 4, 2024
1 parent a198c5d commit 6375f34
Showing 1 changed file with 55 additions and 14 deletions.
69 changes: 55 additions & 14 deletions sphinx_better_subsection.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,30 +34,30 @@ class PreferSectionTarget(Transform):
.. code-block:: xml
...
<target refid="a">
<target refid="b">
<target refid="c">
<section ids="section-title a b c">
<target ids="['a']" names="['a']">
<target ids="['b']" names="['b']">
<target ids="['c']" names="['c']">
<section ids="section-title" names="Section\\ Title">
...
Transforming it gives:
.. code-block:: xml
...
<target refid="a">
<target refid="b">
<target ids="['a']" names="['a']">
<target ids="['b']" names="['b']">
<target refid="c">
<section ids="c section-title a b">
<section ids="c section-title" names="c Section\\ Title">
...
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"""
Expand All @@ -79,14 +79,55 @@ 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
print(last.parent, node)
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"""
Expand Down

0 comments on commit 6375f34

Please sign in to comment.