SF.net SVN: docutils:[10385 ] trunk/docutils
milde--- via Docutils-checkins <[email protected]> Thu, 16 Jul 2026 10:43:24 +0000
| Newsgroups | gmane.text.docutils.cvs |
|---|---|
| Message-ID | <[email protected]> |
Revision: 10385
http://sourceforge.net/p/docutils/code/10385
Author: milde
Date: 2026-07-16 10:43:24 +0000 (Thu, 16 Jul 2026)
Log Message:
-----------
"lazy IDs": handle "chained" targets.
Recursively test the next nodes of a to-be-propagated target; only set IDs
if it is an explicit internal target.
Modified Paths:
--------------
trunk/docutils/HISTORY.rst
trunk/docutils/RELEASE-NOTES.rst
trunk/docutils/docutils/transforms/references.py
trunk/docutils/test/test_transforms/test_hyperlinks.py
Modified: trunk/docutils/HISTORY.rst
===================================================================
--- trunk/docutils/HISTORY.rst 2026-07-16 10:43:14 UTC (rev 10384)
+++ trunk/docutils/HISTORY.rst 2026-07-16 10:43:24 UTC (rev 10385)
@@ -99,18 +99,18 @@
* docutils/transforms/parts.py:
- - `Contents.build_contents()`: ensure sections have an ID and prefer
- IDs from external targets with "lazy IDs" (`legacy_ids`_ False).
+ - "lazy IDs": `Contents.build_contents()` ensures sections have an ID
+ and prefers IDs from external targets if `legacy_ids`_ is False.
* docutils/transforms/references.py
- `IndirectHyperlinks.resolve_indirect_target()` no longer calls
- `unknown_reference_resolvers` and only sets IDs if required.
+ the `unknown_reference_resolvers` hook.
- 3 new transforms, `MatchReferences`, `ReportDanglingReferences`,
and `ReportUnreferencedLinks` obsolete `DanglingReferences`.
- Add INFO system_message if a <target> cannot be propagated
to the next node.
- - support "lazy IDs": Handle hyperlink targets without ID;
+ - "lazy IDs": Handle hyperlink targets without ID;
if required, generate and set one.
* docutils/transforms/universal.py
Modified: trunk/docutils/RELEASE-NOTES.rst
===================================================================
--- trunk/docutils/RELEASE-NOTES.rst 2026-07-16 10:43:14 UTC (rev 10384)
+++ trunk/docutils/RELEASE-NOTES.rst 2026-07-16 10:43:24 UTC (rev 10385)
@@ -233,11 +233,9 @@
rST parser:
- Warn if a `"figure"`_ directive is missing both caption and legend.
- - Don't generate identifiers for indirect or external targets
- (unless legacy_ids_ is True).
- - Generate identifiers for implicit targets (mainly sections) only if
- there is a cross-link to the target [#cross-links]_ and no "explicit"
- identifier (unless legacy_ids_ is True).
+ - "lazy IDs": Generate target ids_ in transforms -- after parsing and
+ only if required in the output document.
+ Keep behaviour backwards compatible with the legacy_ids_ setting.
HTML5 writer:
- Use normal font size and colour for informal titles of type "rubric".
@@ -298,6 +296,7 @@
"`section self-links <section_self_link_>`_" added by the HTML5
writer.
+
Release 0.23 (2026-05-27)
=========================
@@ -1609,8 +1608,9 @@
.. _Docutils Document Model:
.. _Docutils XML: docs/ref/doctree.html
.. _"colwidth" attribute: docs/ref/doctree.html#colwidth
+.. _<doctest_block>: docs/ref/doctree.html#doctest-block
.. _identifier: docs/ref/doctree.html#identifiers
-.. _<doctest_block>: docs/ref/doctree.html#doctest-block
+.. _ids: docs/ref/doctree.html#ids
.. _reference names: docs/ref/doctree.html#reference-names
.. _<target>: docs/ref/doctree.html#target
Modified: trunk/docutils/docutils/transforms/references.py
===================================================================
--- trunk/docutils/docutils/transforms/references.py 2026-07-16 10:43:14 UTC (rev 10384)
+++ trunk/docutils/docutils/transforms/references.py 2026-07-16 10:43:24 UTC (rev 10385)
@@ -69,7 +69,7 @@
if node is None or not self.document.nametypes[name]:
continue
# Skip external or indirect targets:
- if 'refid' in node or 'refname' in node or 'refuri' in node:
+ if not self.is_internal(node):
continue
self.document.set_id(node)
# Now propatate internal <target>s:
@@ -80,15 +80,8 @@
or 'refname' in target
or 'refuri' in target):
continue
- next_node = target.next_node(ascend=True)
- # skip system messages (may be removed by universal.FilterMessages)
- while isinstance(next_node, nodes.system_message):
- next_node = next_node.next_node(ascend=True, descend=False)
- # Do not move names and ids into Invisibles (we'd lose the
- # attributes) or different Targetables (e.g. footnotes).
- if (next_node is None
- or isinstance(next_node, (nodes.Invisible, nodes.Targetable))
- and not isinstance(next_node, nodes.target)):
+ next_node = self.next_suitable_node(target)
+ if next_node is None:
self.document.reporter.info(
f'Cannot propagate target "{" ".join(target["names"])}" '
'to next element', base_node=target)
@@ -135,13 +128,37 @@
self.document.note_refname(target)
elif next_node['names']:
target['refname'] = next_node['names'][0]
- else:
+ elif 'anonymous' not in next_node:
target['refid'] = self.document.set_id(next_node)
target['names'] = []
+ def next_suitable_node(self, target: nodes.Element) -> nodes.Element:
+ if not isinstance(target, nodes.target):
+ return None # only <target> ids/names are propagated
+ candidate = target.next_node(ascend=True)
+ # skip system messages (may be removed by universal.FilterMessages)
+ while isinstance(candidate, nodes.system_message):
+ candidate = candidate.next_node(ascend=True, descend=False)
+ # Do not move names and ids into Invisibles (we'd lose the
+ # attributes) or Targetables (<citation>, <footnote>).
+ # Other <target>s are OK. TODO: why no citations and footnotes?
+ if (isinstance(candidate, (nodes.Invisible, nodes.Targetable))
+ and not isinstance(candidate, nodes.target)):
+ return None
+ return candidate
-class AnonymousHyperlinks(Transform):
+ def is_internal(self, node: nodes.Element) -> bool:
+ # Return True, if `node` is an internal hyperlink target.
+ if 'refid' in node or 'refname' in node or 'refuri' in node:
+ return False # node is indirect or external target
+ # check destination of chained targets:
+ if next_node := self.next_suitable_node(node):
+ return self.is_internal(next_node)
+ return True
+
+class AnonymousHyperlinks(PropagateTargets):
+
"""
Link anonymous references to targets. Given::
@@ -186,6 +203,7 @@
msg.add_backref(prbid)
ref.replace_self(prb)
return
+
for ref, target in zip(anonymous_refs, anonymous_targets):
if ref.hasattr('refid') or ref.hasattr('refuri'):
continue
@@ -201,6 +219,8 @@
target = self.document.ids[target['refid']]
elif 'refname' in target: # indirect target
target = self.document.names[target['refname']]
+ elif next_node := self.next_suitable_node(target):
+ target = next_node
else:
self.document.set_id(target)
continue
@@ -1032,8 +1052,8 @@
naming = target['ids'][0]
else:
# Propagated target: "ids" and "names" attributes moved
- # to the node indicated by "refid" or "refname".
- naming = target.get('refid') or target.get('refname', '???')
+ # to the node indicated by "refname" or "refid".
+ naming = target.get('refname') or target.get('refid', '???')
self.document.reporter.info(
f'Hyperlink target "{naming}" is not referenced.',
base_node=target)
Modified: trunk/docutils/test/test_transforms/test_hyperlinks.py
===================================================================
--- trunk/docutils/test/test_transforms/test_hyperlinks.py 2026-07-16 10:43:14 UTC (rev 10384)
+++ trunk/docutils/test/test_transforms/test_hyperlinks.py 2026-07-16 10:43:24 UTC (rev 10385)
@@ -1300,8 +1300,8 @@
<reference refuri="URI">
target2
, not the Title.
- <target refid="target1">
- <target ids="target1" names="target2 target1" refuri="URI">
+ <target refuri="URI">
+ <target names="target2 target1" refuri="URI">
<section names="title">
<title>
Title
@@ -1401,8 +1401,8 @@
""",
"""\
<document source="test data">
- <target refid="chained">
- <target ids="chained" names="external\\ hyperlink chained" refuri="http://uri">
+ <target refuri="http://uri">
+ <target names="external\\ hyperlink chained" refuri="http://uri">
<paragraph>
<reference refuri="http://uri">
External hyperlink
@@ -1450,7 +1450,7 @@
<document source="test data">
<target names="external\\ hyperlink" refuri="http://uri">
<target refuri="http://uri">
- <target ids="chained" names="indirect\\ hyperlink chained" refuri="http://uri">
+ <target names="indirect\\ hyperlink chained" refuri="http://uri">
<paragraph>
<reference refuri="http://uri">
Chained
@@ -1477,7 +1477,7 @@
<document source="test data">
<target names="external" refuri="http://uri">
<target names="indirect" refuri="http://uri">
- <target refid="internal">
+ <target refname="internal">
<reference ids="internal" names="internal" refuri="http://uri">
<image uri="picture.png">
<reference refuri="http://uri">
@@ -1503,7 +1503,7 @@
""",
"""\
<document source="test data">
- <target refid="img1">
+ <target refname="img1">
<reference ids="img1" names="img1" refuri="uri1.html">
<image uri="pic1.png">
<target anonymous="1" refid="image-1">
@@ -1548,9 +1548,9 @@
""",
"""\
<document source="test data">
- <target anonymous="1" refid="target-1">
- <target anonymous="1" refid="target-1">
- <reference ids="target-1" refuri="uri1.html">
+ <target anonymous="1">
+ <target anonymous="1" refid="reference-1">
+ <reference ids="reference-1" refuri="uri1.html">
<image uri="pic1.png">
<paragraph>
Two \n\
@@ -1560,8 +1560,8 @@
<reference anonymous="1" refuri="uri1.html">
image with target
(sic!).
- <target refid="named">
- <target anonymous="1" refid="named">
+ <target refname="named">
+ <target anonymous="1" refname="named">
<reference ids="named" names="named" refuri="uri2.html">
<image uri="pic2.png">
<paragraph>
@@ -1572,7 +1572,7 @@
anonymous
link to an image with target (sic!).
<target anonymous="1" refname="named link">
- <target refid="named-link">
+ <target refname="named link">
<reference ids="named-link" names="named\\ link" refuri="uri3.html">
<image uri="pic3.png">
<paragraph>
@@ -1590,20 +1590,23 @@
.. _external: http://indirect.external
__ external_
__
+__
`Full syntax anonymous external hyperlink reference`__,
`chained anonymous external reference`__,
`simplified syntax anonymous external hyperlink reference`__,
`indirect anonymous hyperlink reference`__,
-`internal anonymous hyperlink reference`__.
+`internal anonymous hyperlink reference`__,
+`second internal anonymous hyperlink reference`__.
""",
"""\
<document source="test data">
<target anonymous="1" refuri="http://full">
- <target anonymous="1" refid="target-1">
- <target anonymous="1" ids="target-1" refuri="http://simplified">
+ <target anonymous="1">
+ <target anonymous="1" refuri="http://simplified">
<target names="external" refuri="http://indirect.external">
<target anonymous="1" refuri="http://indirect.external">
+ <target anonymous="1">
<target anonymous="1" refid="paragraph-1">
<paragraph ids="paragraph-1">
<reference anonymous="1" refuri="http://full">
@@ -1620,6 +1623,9 @@
,
<reference anonymous="1" refid="paragraph-1">
internal anonymous hyperlink reference
+ ,
+ <reference anonymous="1" refid="paragraph-1">
+ second internal anonymous hyperlink reference
.
"""],
["""\
@@ -1630,8 +1636,8 @@
""",
"""\
<document source="test data">
- <target refid="chained">
- <target anonymous="1" ids="chained" names="chained" refuri="http://anonymous">
+ <target refuri="http://anonymous">
+ <target anonymous="1" names="chained" refuri="http://anonymous">
<paragraph>
<reference anonymous="1" refuri="http://anonymous">
Anonymous
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.