SF.net SVN: docutils:[10303 ] trunk/docutils
milde--- via Docutils-checkins <[email protected]> Fri, 27 Mar 2026 08:41:11 +0000
| Newsgroups | gmane.text.docutils.cvs |
|---|---|
| Message-ID | <[email protected]> |
Revision: 10303
http://sourceforge.net/p/docutils/code/10303
Author: milde
Date: 2026-03-27 08:41:10 +0000 (Fri, 27 Mar 2026)
Log Message:
-----------
Generate identifiers for sections and ToC in transforms.
If the setting "legacy_ids" is False, the rST parser does not generate
IDs for implicit targets. OTOH, writers and the `parts.Contents` transform
rely on section IDs. `transforms.parts.Contents` also expects an identifier
for the `<topic>` containing the table of contents.
New and changed transforms:
`references.SectionIDs`
new, ensures all sections have an identifier.
`references.IndirectHyperlinks?
don't break, if the "relay target" points to an element with
reference-name but no identifier.
`parts.Contents`
sets an identifier for the table of contents.
Modified Paths:
--------------
trunk/docutils/HISTORY.rst
trunk/docutils/RELEASE-NOTES.rst
trunk/docutils/docs/api/transforms.rst
trunk/docutils/docutils/parsers/rst/__init__.py
trunk/docutils/docutils/transforms/parts.py
trunk/docutils/docutils/transforms/references.py
trunk/docutils/test/test_transforms/test_contents.py
trunk/docutils/test/test_transforms/test_hyperlinks.py
trunk/docutils/test/test_transforms/test_sectnum.py
Modified: trunk/docutils/HISTORY.rst
===================================================================
--- trunk/docutils/HISTORY.rst 2026-03-27 08:40:56 UTC (rev 10302)
+++ trunk/docutils/HISTORY.rst 2026-03-27 08:41:10 UTC (rev 10303)
@@ -41,6 +41,7 @@
* docutils/parsers/rst/__init__.py
- New configuration setting `legacy_ids`_.
+ - Load new transform `references.SectionIDs`.
* docutils/parsers/rst/directives/body.py
@@ -74,6 +75,10 @@
- Use `nodes.transition.validate_position()` to warn about transitions
at the beginning or end of the document or a section.
+* docutils/transforms/references.py:
+
+ - New transform `SectionIDs`: ensure all sections have an "identifier".
+
* docutils/writers/html5_polyglot/__init__.py
- Use a section's last "ids" attribute for the "section-self-link".
Modified: trunk/docutils/RELEASE-NOTES.rst
===================================================================
--- trunk/docutils/RELEASE-NOTES.rst 2026-03-27 08:40:56 UTC (rev 10302)
+++ trunk/docutils/RELEASE-NOTES.rst 2026-03-27 08:41:10 UTC (rev 10303)
@@ -323,6 +323,8 @@
`nodes.document.set_duplicate_name()`
Called by `nodes.document.note_names()` to handle duplicate names.
Provisional.
+ `transforms.SectionIDs`:
+ Ensure all sections have an identifier_.
Removed objects
`parsers.rst.directives.tables.CSVTable.check_requirements()`
@@ -1595,6 +1597,7 @@
.. _Docutils Document Model:
.. _Docutils XML: docs/ref/doctree.html
.. _"colwidth" attribute: docs/ref/doctree.html#colwidth
+.. _identifier: docs/ref/doctree.html#identifiers
.. _<doctest_block>: docs/ref/doctree.html#doctest-block
.. _reference names: docs/ref/doctree.html#reference-names
.. _<target>: docs/ref/doctree.html#target
Modified: trunk/docutils/docs/api/transforms.rst
===================================================================
--- trunk/docutils/docs/api/transforms.rst 2026-03-27 08:40:56 UTC (rev 10302)
+++ trunk/docutils/docs/api/transforms.rst 2026-03-27 08:41:10 UTC (rev 10303)
@@ -57,6 +57,8 @@
references_.Substitutions standalone_ (r), pep_ (r) _`220`
+references_.SectionIDs rst_ (p) _`240`
+
references_.PropagateTargets standalone_ (r), pep_ (r) _`260`
frontmatter.\ DocTitle_ standalone_ (r) _`320`
@@ -220,7 +222,8 @@
.. _rst:
parsers.rst.Parser
- universal.SmartQuotes (855_)
+ | references.SectionIDs (240_)
+ | universal.SmartQuotes (855_)
.. _writers:
Modified: trunk/docutils/docutils/parsers/rst/__init__.py
===================================================================
--- trunk/docutils/docutils/parsers/rst/__init__.py 2026-03-27 08:40:56 UTC (rev 10302)
+++ trunk/docutils/docutils/parsers/rst/__init__.py 2026-03-27 08:41:10 UTC (rev 10303)
@@ -75,7 +75,7 @@
import docutils.statemachine
from docutils.parsers.rst import roles, states
from docutils import frontend, nodes
-from docutils.transforms import universal
+from docutils.transforms import references, universal
class Parser(docutils.parsers.Parser):
@@ -169,7 +169,8 @@
self.inliner = inliner
def get_transforms(self):
- return super().get_transforms() + [universal.SmartQuotes]
+ return [*super().get_transforms(),
+ references.SectionIDs, universal.SmartQuotes]
def parse(self, inputstring, document) -> None:
"""Parse `inputstring` and populate `document`, a document tree."""
Modified: trunk/docutils/docutils/transforms/parts.py
===================================================================
--- trunk/docutils/docutils/transforms/parts.py 2026-03-27 08:40:56 UTC (rev 10302)
+++ trunk/docutils/docutils/transforms/parts.py 2026-03-27 08:41:10 UTC (rev 10303)
@@ -86,6 +86,8 @@
default_priority = 720
def apply(self) -> None:
+ # ensure the "ToC topic" wrapper element has an identifier:
+ self.toc_id = self.document.set_id(self.startnode.parent)
# let the writer (or output software) build the contents list?
toc_by_writer = getattr(self.document.settings, 'use_latex_toc', False)
# TODO: handle "generate_oowriter_toc" setting of the "ODT" writer.
@@ -100,7 +102,6 @@
startnode = startnode.parent
else:
startnode = self.document
- self.toc_id = self.startnode.parent['ids'][0]
if 'backlinks' in details:
self.backlinks = details['backlinks']
else:
Modified: trunk/docutils/docutils/transforms/references.py
===================================================================
--- trunk/docutils/docutils/transforms/references.py 2026-03-27 08:40:56 UTC (rev 10302)
+++ trunk/docutils/docutils/transforms/references.py 2026-03-27 08:41:10 UTC (rev 10303)
@@ -14,9 +14,29 @@
from docutils.transforms import Transform
-class PropagateTargets(Transform):
+class SectionIDs(Transform):
+ """
+ Add identifiers to sections.
+ If the "legacy_ids" configuration setting is False, the rST parser
+ does not generate identifiers for implicit targets (e.g. sections)
+ in order to give explicit targets preferential access to identifiers
+ matching their reference name.
+
+ However, the `parts.Contents` transform and most writers
+ expect sections to have an identifier, so this transform adds them.
"""
+ default_priority = 240
+
+ def apply(self) -> None:
+ if getattr(self.document.settings, "legacy_ids", True):
+ return
+ for node in self.document.findall(nodes.section):
+ self.document.set_id(node)
+
+
+class PropagateTargets(Transform):
+ """
Propagate empty internal targets to the next element.
Given the following nodes::
@@ -222,22 +242,26 @@
self.resolve_indirect_references(target)
def resolve_indirect_target(self, target) -> None:
+ # indirect targets have either a refname or refid attribute
refname = target.get('refname')
- if refname is None:
- reftarget_id = target['refid']
+ refid = target.get('refid')
+ if refid:
+ reftarget = self.document.ids.get(refid)
else:
- reftarget_id = self.document.nameids.get(refname)
- if not reftarget_id:
- # Check the unknown_reference_resolvers
- for resolver_function in \
- self.document.transformer.unknown_reference_resolvers:
- if resolver_function(target):
- break
- else:
- self.nonexistent_indirect_target(target)
- return
- reftarget = self.document.ids[reftarget_id]
- reftarget.note_referenced_by(id=reftarget_id)
+ reftarget = self.document.names.get(refname)
+ refid = self.document.nameids.get(refname)
+ if reftarget and not refid:
+ refid = self.document.set_id(reftarget)
+ if not reftarget:
+ # Check the unknown_reference_resolvers
+ for resolver_function in \
+ self.document.transformer.unknown_reference_resolvers:
+ if resolver_function(target):
+ break
+ else:
+ self.nonexistent_indirect_target(target)
+ return
+ reftarget.note_referenced_by(id=refid)
if (isinstance(reftarget, nodes.target)
and not reftarget.resolved
and reftarget.hasattr('refname')):
@@ -256,7 +280,7 @@
self.document.note_refid(target)
else:
if reftarget['ids']:
- target['refid'] = reftarget_id
+ target['refid'] = refid
self.document.note_refid(target)
else:
self.nonexistent_indirect_target(target)
@@ -266,7 +290,7 @@
target.resolved = True
def nonexistent_indirect_target(self, target) -> None:
- if target['refname'] in self.document.nameids:
+ if self.document.names.get(target['refname'], '') is None:
self.indirect_target_error(target, 'which is a duplicate, and '
'cannot be used as a unique reference')
else:
Modified: trunk/docutils/test/test_transforms/test_contents.py
===================================================================
--- trunk/docutils/test/test_transforms/test_contents.py 2026-03-27 08:40:56 UTC (rev 10302)
+++ trunk/docutils/test/test_transforms/test_contents.py 2026-03-27 08:41:10 UTC (rev 10303)
@@ -20,7 +20,7 @@
from docutils.frontend import get_default_settings
from docutils.parsers.rst import Parser
-from docutils.transforms.references import Substitutions
+from docutils.transforms.references import SectionIDs, Substitutions
from docutils.transforms.universal import TestMessages
from docutils.utils import new_document
@@ -46,7 +46,7 @@
totest = {}
-totest['tables_of_contents'] = ((Substitutions,), [
+totest['tables_of_contents'] = ((SectionIDs, Substitutions,), [
["""\
.. contents::
Modified: trunk/docutils/test/test_transforms/test_hyperlinks.py
===================================================================
--- trunk/docutils/test/test_transforms/test_hyperlinks.py 2026-03-27 08:40:56 UTC (rev 10302)
+++ trunk/docutils/test/test_transforms/test_hyperlinks.py 2026-03-27 08:41:10 UTC (rev 10303)
@@ -21,7 +21,7 @@
from docutils.parsers.rst import Parser
from docutils.transforms.references import PropagateTargets, \
AnonymousHyperlinks, IndirectHyperlinks, ExternalTargets, \
- InternalTargets, DanglingReferences
+ InternalTargets, DanglingReferences, SectionIDs
from docutils.transforms.universal import TestMessages
from docutils.utils import new_document
@@ -31,7 +31,7 @@
transforms = (PropagateTargets, AnonymousHyperlinks, IndirectHyperlinks,
ExternalTargets, InternalTargets, DanglingReferences,
- TestMessages)
+ SectionIDs, TestMessages)
def test_transforms(self):
parser = Parser()
@@ -1100,7 +1100,9 @@
"""],
])
-totest['hyperlinks'] = ({'legacy_ids': False}, [
+totest['hyperlinks'] = ({'legacy_ids': False},
+ # all but the last sample compile as before
+ totest['hyperlinks legacy'][1][:-1] + [
["""\
foo
---
@@ -1114,7 +1116,7 @@
""",
"""\
<document source="test data">
- <section dupnames="foo">
+ <section dupnames="foo" ids="foo-1">
<title>
foo
<system_message backrefs="foo" level="1" line="5" source="test data" type="INFO">
Modified: trunk/docutils/test/test_transforms/test_sectnum.py
===================================================================
--- trunk/docutils/test/test_transforms/test_sectnum.py 2026-03-27 08:40:56 UTC (rev 10302)
+++ trunk/docutils/test/test_transforms/test_sectnum.py 2026-03-27 08:41:10 UTC (rev 10303)
@@ -20,7 +20,7 @@
from docutils.frontend import get_default_settings
from docutils.parsers.rst import Parser
-from docutils.transforms.references import Substitutions
+from docutils.transforms.references import SectionIDs, Substitutions
from docutils.transforms.universal import TestMessages
from docutils.utils import new_document
@@ -46,7 +46,7 @@
totest = {}
-totest['section_numbers'] = ((Substitutions,), [
+totest['section_numbers'] = ((SectionIDs, Substitutions), [
["""\
.. sectnum::
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.