SF.net SVN: docutils:[10383 ] trunk/docutils

milde--- via Docutils-checkins <[email protected]> Wed, 15 Jul 2026 18:57:56 +0000
Newsgroups gmane.text.docutils.cvs
Message-ID <[email protected]>
Revision: 10383
          http://sourceforge.net/p/docutils/code/10383
Author:   milde
Date:     2026-07-15 18:57:56 +0000 (Wed, 15 Jul 2026)
Log Message:
-----------
"lazy IDs": postpone generation of IDs for explicit targets.

Generate IDs for explicit targets after parsing is complete:

`nodes.document.note_explicit_target()` only generates an ID for the target,
if the "legacy_ids" setting is True.

The `transforms.references.PropagateTargets` transform adds IDs to
internal explicit targets (which may also be <topics> or other elements)
before "propagating" empty targets.

`transforms.references.Footnotes.number_footnote_references()`
sets IDs on footnotes with "reference name" but no ID.

This is a precondition for a future check whether the destination of
to-be-propagated targets is an external or indirect target that does
not require an ID. For example in
~~~
.. _e.g.:
.. _example: eg.html
~~~
the second target may become `<target names="example e.g." refuri="eg.html">`
instead of `<target ids="e-g" names="example e.g." refuri="eg.html">`.

Adapt tests: the output of parser tests has no IDs for explicit targets,
nor has the output of transform tests that do not run the `PropagateTargets`
transform.

Modified Paths:
--------------
    trunk/docutils/docutils/nodes.py
    trunk/docutils/docutils/transforms/references.py
    trunk/docutils/test/test_parsers/test_rst/test_directives/test_include.py
    trunk/docutils/test/test_parsers/test_rst/test_inline_markup.py
    trunk/docutils/test/test_parsers/test_rst/test_targets.py
    trunk/docutils/test/test_transforms/test_doctitle.py
    trunk/docutils/test/test_transforms/test_hyperlinks.py
    trunk/docutils/test/test_transforms/test_transitions.py

Modified: trunk/docutils/docutils/nodes.py
===================================================================
--- trunk/docutils/docutils/nodes.py	2026-07-14 13:32:05 UTC (rev 10382)
+++ trunk/docutils/docutils/nodes.py	2026-07-15 18:57:56 UTC (rev 10383)
@@ -2177,8 +2177,7 @@
     def note_explicit_target(self, target: Element,
                              msgnode: Element|None = None) -> None:
         self.note_names(target, msgnode, explicit=True)
-        if (getattr(self.settings, "legacy_ids", True)
-            or 'refuri' not in target and 'refname' not in target):
+        if getattr(self.settings, "legacy_ids", True):
             self.set_id(target, msgnode)
 
     def note_refname(self, node: Element) -> None:

Modified: trunk/docutils/docutils/transforms/references.py
===================================================================
--- trunk/docutils/docutils/transforms/references.py	2026-07-14 13:32:05 UTC (rev 10382)
+++ trunk/docutils/docutils/transforms/references.py	2026-07-15 18:57:56 UTC (rev 10383)
@@ -42,14 +42,15 @@
 
     Given the following nodes::
 
-        <target ids="internal1" names="internal1">
+        <target names="internal1">
         <target anonymous="1" ids="id1">
-        <target ids="internal2" names="internal2">
+        <target names="internal2">
         <paragraph>
             This is a test.
 
-    `PropagateTargets` propagates the ids and names of the internal
-    targets preceding the paragraph to the paragraph itself::
+    `PropagateTargets` ensures internal targets have an ID and propagates
+    the IDs and names of the internal targets preceding the paragraph
+    to the paragraph itself::
 
         <target refid="internal1">
         <target anonymous="1" refid="id1">
@@ -61,6 +62,17 @@
     default_priority = 260
 
     def apply(self) -> None:
+        # Ensure explicit internal targets ("named" elements) have an ID:
+        if not getattr(self.document.settings, "legacy_ids", True):
+            for name, node in self.document.names.items():
+                # Skip targets with duplicate or implicit names:
+                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:
+                    continue
+                self.document.set_id(node)
+        # Now propatate internal <target>s:
         for target in self.document.findall(nodes.target):
             # Only block-level targets without reference (like ".. _target:"):
             if (len(target) or isinstance(target.parent, nodes.TextElement)
@@ -590,8 +602,9 @@
                     ref.replace_self(prb)
                 break
             ref += nodes.Text(label)
-            id = self.document.nameids[label]
-            footnote = self.document.ids[id]
+            footnote = self.document.names[label]
+            id = (self.document.nameids.get(label)
+                  or self.document.set_id(footnote))
             ref['refid'] = id
             self.document.note_refid(ref)
             assert len(ref['ids']) == 1

Modified: trunk/docutils/test/test_parsers/test_rst/test_directives/test_include.py
===================================================================
--- trunk/docutils/test/test_parsers/test_rst/test_directives/test_include.py	2026-07-14 13:32:05 UTC (rev 10382)
+++ trunk/docutils/test/test_parsers/test_rst/test_directives/test_include.py	2026-07-15 18:57:56 UTC (rev 10383)
@@ -179,7 +179,7 @@
     <section names="include\\ test">
         <title>
             Include Test
-        <literal_block classes="test" ids="my-name" names="my\\ name" source="{include1}" xml:space="preserve">
+        <literal_block classes="test" names="my\\ name" source="{include1}" xml:space="preserve">
             Inclusion 1
             -----------
             \n\
@@ -244,7 +244,7 @@
 <document source="test data">
     <paragraph>
         Include code
-    <literal_block classes="code test" ids="my-name" names="my\\ name" source="{include1}" xml:space="preserve">
+    <literal_block classes="code test" names="my\\ name" source="{include1}" xml:space="preserve">
         Inclusion 1
         -----------
         \n\

Modified: trunk/docutils/test/test_parsers/test_rst/test_inline_markup.py
===================================================================
--- trunk/docutils/test/test_parsers/test_rst/test_inline_markup.py	2026-07-14 13:32:05 UTC (rev 10382)
+++ trunk/docutils/test/test_parsers/test_rst/test_inline_markup.py	2026-07-15 18:57:56 UTC (rev 10383)
@@ -1168,10 +1168,10 @@
         Report duplicate refname.
     <paragraph>
         Explicit targets: \n\
-        <target ids="file-txt" names="file.txt">
+        <target names="file.txt">
             file.txt
         , \n\
-        <target ids="file-html" names="file.html">
+        <target names="file.html">
             file.html
         .
     <system_message level="1" line="6" source="test data" type="INFO">
@@ -1295,10 +1295,10 @@
         Duplicate refnames in references with embedded alias.
     <paragraph>
         Explicit targets: \n\
-        <target ids="tg1" names="tg1">
+        <target names="tg1">
             tg1
          and \n\
-        <target ids="tg2" names="tg2">
+        <target names="tg2">
             tg2
         .
     <system_message level="1" line="6" source="test data" type="INFO">
@@ -1334,19 +1334,19 @@
 """\
 <document source="test data">
     <paragraph>
-        <target ids="target" names="target">
+        <target names="target">
             target
     <paragraph>
         Here is \n\
-        <target ids="another-target" names="another\\ target">
+        <target names="another\\ target">
             another target
          in some text. And \n\
-        <target ids="yet-another-target" names="yet\\ another\\ target">
+        <target names="yet\\ another\\ target">
             yet
             another target
         , spanning lines.
     <paragraph>
-        <target ids="here-is-a-target" names="here\\ is\\ a\\ target">
+        <target names="here\\ is\\ a\\ target">
             Here is  a    TaRgeT
          with case and spacial difficulties.
 """],
@@ -1357,10 +1357,10 @@
 <document source="test data">
     <paragraph>
         l'
-        <target ids="target1" names="target1">
+        <target names="target1">
             target1
          and l\u2019
-        <target ids="target2" names="target2">
+        <target names="target2">
             target2
          with apostrophe
 """],
@@ -1373,21 +1373,21 @@
 <document source="test data">
     <paragraph>
         quoted '
-        <target ids="target1" names="target1">
+        <target names="target1">
             target1
         ', quoted "
-        <target ids="target2" names="target2">
+        <target names="target2">
             target2
         ",
         quoted \u2018
-        <target ids="target3" names="target3">
+        <target names="target3">
             target3
         \u2019, quoted \u201c
-        <target ids="target4" names="target4">
+        <target names="target4">
             target4
         \u201d,
         quoted \xab
-        <target ids="target5" names="target5">
+        <target names="target5">
             target5
         \xbb
 """],
@@ -1399,19 +1399,19 @@
 """\
 <document source="test data">
     <paragraph>
-        <target ids="target1" names="'target1'">
+        <target names="'target1'">
             'target1'
          with quotes, \n\
-        <target ids="target2" names=""target2"">
+        <target names=""target2"">
             "target2"
          with quotes,
-        <target ids="target3" names="\u2018target3\u2019">
+        <target names="\u2018target3\u2019">
             \u2018target3\u2019
          with quotes, \n\
-        <target ids="target4" names="\u201ctarget4\u201d">
+        <target names="\u201ctarget4\u201d">
             \u201ctarget4\u201d
          with quotes,
-        <target ids="target5" names="\xabtarget5\xbb">
+        <target names="\xabtarget5\xbb">
             \xabtarget5\xbb
          with quotes
 """],

Modified: trunk/docutils/test/test_parsers/test_rst/test_targets.py
===================================================================
--- trunk/docutils/test/test_parsers/test_rst/test_targets.py	2026-07-14 13:32:05 UTC (rev 10382)
+++ trunk/docutils/test/test_parsers/test_rst/test_targets.py	2026-07-15 18:57:56 UTC (rev 10383)
@@ -53,7 +53,7 @@
 """,
 """\
 <document source="test data">
-    <target ids="target" names="target">
+    <target names="target">
     <paragraph>
         (Internal hyperlink target.)
 """],
@@ -62,7 +62,7 @@
 """,
 """\
 <document source="test data">
-    <target ids="optional-space-before-colon" names="optional\\ space\\ before\\ colon">
+    <target names="optional\\ space\\ before\\ colon">
 """],
 [r"""
 External hyperlink targets:
@@ -115,9 +115,9 @@
 """,
 """\
 <document source="test data">
-    <target ids="a-long-target-name" names="a\\ long\\ target\\ name">
-    <target ids="a-target-name-including-a-colon-quoted" names="a\\ target\\ name:\\ including\\ a\\ colon\\ (quoted)">
-    <target ids="a-target-name-including-a-colon-escaped" names="a\\ target\\ name:\\ including\\ a\\ colon\\ (escaped)">
+    <target names="a\\ long\\ target\\ name">
+    <target names="a\\ target\\ name:\\ including\\ a\\ colon\\ (quoted)">
+    <target names="a\\ target\\ name:\\ including\\ a\\ colon\\ (escaped)">
 """],
 ["""\
 .. _`target: No matching backquote.
@@ -144,8 +144,8 @@
 """,
 """\
 <document source="test data">
-    <target ids="a-very-long-target-name-split-across-lines" names="a\\ very\\ long\\ target\\ name,\\ split\\ across\\ lines">
-    <target ids="and-another-with-backquotes" names="and\\ another,\\ with\\ backquotes">
+    <target names="a\\ very\\ long\\ target\\ name,\\ split\\ across\\ lines">
+    <target names="and\\ another,\\ with\\ backquotes">
 """],
 ["""\
 External hyperlink:
@@ -265,7 +265,7 @@
 <document source="test data">
     <paragraph>
         Duplicate indirect \n\
-        <target ids="targets" names="targets">
+        <target names="targets">
             targets
          (same refname):
     <target names="link" refname="targets">
@@ -330,7 +330,7 @@
         <system_message level="1" line="6" source="test data" type="INFO">
             <paragraph>
                 Target name overrides implicit target name "title".
-        <target ids="title" names="title">
+        <target names="title">
         <paragraph>
             Paragraph.
 """],
@@ -376,19 +376,19 @@
 <document source="test data">
     <paragraph>
         Duplicate explicit targets.
-    <target dupnames="title" ids="title">
+    <target dupnames="title">
     <paragraph>
         First.
     <system_message level="2" line="7" source="test data" type="WARNING">
         <paragraph>
             Duplicate explicit target name: "title".
-    <target dupnames="title" ids="title-1">
+    <target dupnames="title">
     <paragraph>
         Second.
     <system_message level="2" line="11" source="test data" type="WARNING">
         <paragraph>
             Duplicate explicit target name: "title".
-    <target dupnames="title" ids="title-2">
+    <target dupnames="title">
     <paragraph>
         Third.
 """],
@@ -409,7 +409,7 @@
 <document source="test data">
     <paragraph>
         Duplicate explicit/directive targets.
-    <target dupnames="title" ids="title">
+    <target dupnames="title">
     <paragraph>
         First.
     <rubric dupnames="title" ids="title-1">
@@ -471,7 +471,7 @@
         <system_message level="2" line="12" source="test data" type="WARNING">
             <paragraph>
                 Duplicate explicit target name: "target".
-        <target dupnames="target" ids="target-2">
+        <target dupnames="target">
         <paragraph>
             Explicit internal target.
         <system_message level="2" line="16" source="test data" type="WARNING">
@@ -482,10 +482,10 @@
             <line>
                 Do not insert <system_message> element for duplicate
             <line>
-                <target dupnames="target" ids="target-3">
+                <target dupnames="target" ids="target-2">
                     target
                 , if this results in an invalid doctree.
-        <rubric dupnames="target" ids="target-4">
+        <rubric dupnames="target" ids="target-3">
             directive with target
         <field_list>
             <field>
@@ -496,7 +496,7 @@
                         with
             <field>
                 <field_name>
-                    <target dupnames="target" ids="target-5">
+                    <target dupnames="target" ids="target-4">
                         target
                 <field_body>
                     <paragraph>

Modified: trunk/docutils/test/test_transforms/test_doctitle.py
===================================================================
--- trunk/docutils/test/test_transforms/test_doctitle.py	2026-07-14 13:32:05 UTC (rev 10382)
+++ trunk/docutils/test/test_transforms/test_doctitle.py	2026-07-15 18:57:56 UTC (rev 10383)
@@ -227,7 +227,7 @@
         Title
     <substitution_definition names="foo">
         bar
-    <target ids="invisible-target" names="invisible\\ target">
+    <target names="invisible\\ target">
     <paragraph>
         This title should be the document title despite the
         substitution_definition.

Modified: trunk/docutils/test/test_transforms/test_hyperlinks.py
===================================================================
--- trunk/docutils/test/test_transforms/test_hyperlinks.py	2026-07-14 13:32:05 UTC (rev 10382)
+++ trunk/docutils/test/test_transforms/test_hyperlinks.py	2026-07-15 18:57:56 UTC (rev 10383)
@@ -1485,7 +1485,105 @@
     <reference refid="internal">
         <image uri="picture.png">
 """],
+# TODO: Anonymous links to a "clickable image should go
+#       to the image, not the image's click-target!
 ["""\
+.. _img1:
+.. image:: pic1.png
+   :target: uri1.html
+__
+.. image:: pic2.png
+__
+.. image:: pic3.png
+   :target: uri3.html
+
+Named link to img1_ with target and anonymous links to
+the targetless img2__ and
+img3__ with target (sic!).
+""",
+"""\
+<document source="test data">
+    <target refid="img1">
+    <reference ids="img1" names="img1" refuri="uri1.html">
+        <image uri="pic1.png">
+    <target anonymous="1" refid="target-1">
+    <image ids="target-1" uri="pic2.png">
+    <target anonymous="1" refid="target-2">
+    <reference ids="target-2" refuri="uri3.html">
+        <image uri="pic3.png">
+    <paragraph>
+        Named link to \n\
+        <reference refid="img1">
+            img1
+         with target and anonymous links to
+        the targetless \n\
+        <reference anonymous="1" refid="target-1">
+            img2
+         and
+        <reference anonymous="1" refuri="uri3.html">
+            img3
+         with target (sic!).
+"""],
+["""\
+__
+__
+.. image:: pic1.png
+   :target: uri1.html
+
+Two anonymous__ links to an `image with target`__ (sic!).
+
+.. _named:
+__
+.. image:: pic2.png
+   :target: uri2.html
+
+Named_ and anonymous__ link to an image with target (sic!).
+
+__
+.. _named link:
+.. image:: pic3.png
+   :target: uri3.html
+
+Anonymous__ and `named link`_ to an image with target (sic!).
+""",
+"""\
+<document source="test data">
+    <target anonymous="1" refid="target-1">
+    <target anonymous="1" refid="target-2">
+    <reference ids="target-2 target-1" refuri="uri1.html">
+        <image uri="pic1.png">
+    <paragraph>
+        Two \n\
+        <reference anonymous="1" refuri="uri1.html">
+            anonymous
+         links to an \n\
+        <reference anonymous="1" refuri="uri1.html">
+            image with target
+         (sic!).
+    <target refid="named">
+    <target anonymous="1" refid="target-3">
+    <reference ids="target-3 named" names="named" refuri="uri2.html">
+        <image uri="pic2.png">
+    <paragraph>
+        <reference refid="named">
+            Named
+         and \n\
+        <reference anonymous="1" refuri="uri2.html">
+            anonymous
+         link to an image with target (sic!).
+    <target anonymous="1" refid="target-4">
+    <target refid="named-link">
+    <reference ids="named-link target-4" names="named\\ link" refuri="uri3.html">
+        <image uri="pic3.png">
+    <paragraph>
+        <reference anonymous="1" refuri="uri3.html">
+            Anonymous
+         and \n\
+        <reference refid="named-link">
+            named link
+         to an image with target (sic!).
+"""],
+["""\
 .. __: http://full
 __
 __ http://simplified

Modified: trunk/docutils/test/test_transforms/test_transitions.py
===================================================================
--- trunk/docutils/test/test_transforms/test_transitions.py	2026-07-14 13:32:05 UTC (rev 10382)
+++ trunk/docutils/test/test_transforms/test_transitions.py	2026-07-15 18:57:56 UTC (rev 10383)
@@ -198,7 +198,7 @@
     <system_message level="2" line="3" source="test data" type="WARNING">
         <paragraph>
             Transition at the end of the document.
-    <target ids="anchor" names="anchor">
+    <target names="anchor">
     <substitution_definition names="substitution\\ reference">
         is invisible
 """],
@@ -316,7 +316,7 @@
         <footer>
             <paragraph>
                 will move away
-    <target ids="anchor" names="anchor">
+    <target names="anchor">
     <substitution_definition names="substitution\\ reference">
         is invisible
     <transition classes="classy">

This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.