You can subscribe to this list here.
| 2002 | 
          Jan
           | 
        
        
        
        
          Feb
           | 
        
        
        
        
          Mar
           | 
        
        
        
        
          Apr
           (106)  | 
        
        
        
        
          May
           (215)  | 
        
        
        
        
          Jun
           (104)  | 
        
        
        
        
          Jul
           (290)  | 
        
        
        
        
          Aug
           (351)  | 
        
        
        
        
          Sep
           (245)  | 
        
        
        
        
          Oct
           (289)  | 
        
        
        
        
          Nov
           (184)  | 
        
        
        
        
          Dec
           (113)  | 
        
      
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2003 | 
          Jan
           (179)  | 
        
        
        
        
          Feb
           (88)  | 
        
        
        
        
          Mar
           (77)  | 
        
        
        
        
          Apr
           (70)  | 
        
        
        
        
          May
           (107)  | 
        
        
        
        
          Jun
           (288)  | 
        
        
        
        
          Jul
           (115)  | 
        
        
        
        
          Aug
           (67)  | 
        
        
        
        
          Sep
           (91)  | 
        
        
        
        
          Oct
           (34)  | 
        
        
        
        
          Nov
           (31)  | 
        
        
        
        
          Dec
           (61)  | 
        
      
| 2004 | 
          Jan
           (54)  | 
        
        
        
        
          Feb
           (17)  | 
        
        
        
        
          Mar
           (102)  | 
        
        
        
        
          Apr
           (152)  | 
        
        
        
        
          May
           (178)  | 
        
        
        
        
          Jun
           (377)  | 
        
        
        
        
          Jul
           (136)  | 
        
        
        
        
          Aug
           (37)  | 
        
        
        
        
          Sep
           (196)  | 
        
        
        
        
          Oct
           (142)  | 
        
        
        
        
          Nov
           (119)  | 
        
        
        
        
          Dec
           (58)  | 
        
      
| 2005 | 
          Jan
           (51)  | 
        
        
        
        
          Feb
           (76)  | 
        
        
        
        
          Mar
           (220)  | 
        
        
        
        
          Apr
           (132)  | 
        
        
        
        
          May
           (134)  | 
        
        
        
        
          Jun
           (230)  | 
        
        
        
        
          Jul
           (142)  | 
        
        
        
        
          Aug
           (58)  | 
        
        
        
        
          Sep
           (71)  | 
        
        
        
        
          Oct
           (76)  | 
        
        
        
        
          Nov
           (129)  | 
        
        
        
        
          Dec
           (117)  | 
        
      
| 2006 | 
          Jan
           (94)  | 
        
        
        
        
          Feb
           (30)  | 
        
        
        
        
          Mar
           (97)  | 
        
        
        
        
          Apr
           (63)  | 
        
        
        
        
          May
           (63)  | 
        
        
        
        
          Jun
           (62)  | 
        
        
        
        
          Jul
           (23)  | 
        
        
        
        
          Aug
           (40)  | 
        
        
        
        
          Sep
           (47)  | 
        
        
        
        
          Oct
           (40)  | 
        
        
        
        
          Nov
           (23)  | 
        
        
        
        
          Dec
           (21)  | 
        
      
| 2007 | 
          Jan
           (57)  | 
        
        
        
        
          Feb
           (65)  | 
        
        
        
        
          Mar
           (77)  | 
        
        
        
        
          Apr
           (23)  | 
        
        
        
        
          May
           (118)  | 
        
        
        
        
          Jun
           (127)  | 
        
        
        
        
          Jul
           (87)  | 
        
        
        
        
          Aug
           (33)  | 
        
        
        
        
          Sep
           (26)  | 
        
        
        
        
          Oct
           (8)  | 
        
        
        
        
          Nov
           (4)  | 
        
        
        
        
          Dec
           (25)  | 
        
      
| 2008 | 
          Jan
           (16)  | 
        
        
        
        
          Feb
           (18)  | 
        
        
        
        
          Mar
           (16)  | 
        
        
        
        
          Apr
           (4)  | 
        
        
        
        
          May
           (22)  | 
        
        
        
        
          Jun
           (20)  | 
        
        
        
        
          Jul
           (38)  | 
        
        
        
        
          Aug
           (14)  | 
        
        
        
        
          Sep
           (18)  | 
        
        
        
        
          Oct
           (68)  | 
        
        
        
        
          Nov
           (16)  | 
        
        
        
        
          Dec
           (95)  | 
        
      
| 2009 | 
          Jan
           (28)  | 
        
        
        
        
          Feb
           (16)  | 
        
        
        
        
          Mar
           (8)  | 
        
        
        
        
          Apr
           (44)  | 
        
        
        
        
          May
           (35)  | 
        
        
        
        
          Jun
           (41)  | 
        
        
        
        
          Jul
           (63)  | 
        
        
        
        
          Aug
           (40)  | 
        
        
        
        
          Sep
           (38)  | 
        
        
        
        
          Oct
           (41)  | 
        
        
        
        
          Nov
           (17)  | 
        
        
        
        
          Dec
           (9)  | 
        
      
| 2010 | 
          Jan
           (9)  | 
        
        
        
        
          Feb
           (3)  | 
        
        
        
        
          Mar
           (71)  | 
        
        
        
        
          Apr
           (20)  | 
        
        
        
        
          May
           (15)  | 
        
        
        
        
          Jun
           (16)  | 
        
        
        
        
          Jul
           (33)  | 
        
        
        
        
          Aug
           (13)  | 
        
        
        
        
          Sep
           (39)  | 
        
        
        
        
          Oct
           (30)  | 
        
        
        
        
          Nov
           (25)  | 
        
        
        
        
          Dec
           (20)  | 
        
      
| 2011 | 
          Jan
           (213)  | 
        
        
        
        
          Feb
           (252)  | 
        
        
        
        
          Mar
           (24)  | 
        
        
        
        
          Apr
           (24)  | 
        
        
        
        
          May
           (20)  | 
        
        
        
        
          Jun
           (21)  | 
        
        
        
        
          Jul
           (37)  | 
        
        
        
        
          Aug
           (18)  | 
        
        
        
        
          Sep
           (28)  | 
        
        
        
        
          Oct
           (65)  | 
        
        
        
        
          Nov
           (22)  | 
        
        
        
        
          Dec
           (48)  | 
        
      
| 2012 | 
          Jan
           (35)  | 
        
        
        
        
          Feb
           (39)  | 
        
        
        
        
          Mar
           (17)  | 
        
        
        
        
          Apr
           (9)  | 
        
        
        
        
          May
           (37)  | 
        
        
        
        
          Jun
           (31)  | 
        
        
        
        
          Jul
           (23)  | 
        
        
        
        
          Aug
           (14)  | 
        
        
        
        
          Sep
           (16)  | 
        
        
        
        
          Oct
           (15)  | 
        
        
        
        
          Nov
           (5)  | 
        
        
        
        
          Dec
           (43)  | 
        
      
| 2013 | 
          Jan
           (15)  | 
        
        
        
        
          Feb
           (19)  | 
        
        
        
        
          Mar
           (26)  | 
        
        
        
        
          Apr
           (13)  | 
        
        
        
        
          May
           (9)  | 
        
        
        
        
          Jun
           (11)  | 
        
        
        
        
          Jul
           (32)  | 
        
        
        
        
          Aug
           (9)  | 
        
        
        
        
          Sep
           (6)  | 
        
        
        
        
          Oct
           | 
        
        
        
        
          Nov
           (13)  | 
        
        
        
        
          Dec
           (5)  | 
        
      
| 2014 | 
          Jan
           (2)  | 
        
        
        
        
          Feb
           (3)  | 
        
        
        
        
          Mar
           (1)  | 
        
        
        
        
          Apr
           | 
        
        
        
        
          May
           (2)  | 
        
        
        
        
          Jun
           (4)  | 
        
        
        
        
          Jul
           (18)  | 
        
        
        
        
          Aug
           | 
        
        
        
        
          Sep
           | 
        
        
        
        
          Oct
           (3)  | 
        
        
        
        
          Nov
           (4)  | 
        
        
        
        
          Dec
           (2)  | 
        
      
| 2015 | 
          Jan
           (3)  | 
        
        
        
        
          Feb
           (25)  | 
        
        
        
        
          Mar
           (49)  | 
        
        
        
        
          Apr
           (28)  | 
        
        
        
        
          May
           (13)  | 
        
        
        
        
          Jun
           (2)  | 
        
        
        
        
          Jul
           (2)  | 
        
        
        
        
          Aug
           (14)  | 
        
        
        
        
          Sep
           (9)  | 
        
        
        
        
          Oct
           (6)  | 
        
        
        
        
          Nov
           | 
        
        
        
        
          Dec
           (2)  | 
        
      
| 2016 | 
          Jan
           (2)  | 
        
        
        
        
          Feb
           (1)  | 
        
        
        
        
          Mar
           | 
        
        
        
        
          Apr
           | 
        
        
        
        
          May
           (12)  | 
        
        
        
        
          Jun
           | 
        
        
        
        
          Jul
           (17)  | 
        
        
        
        
          Aug
           (7)  | 
        
        
        
        
          Sep
           (3)  | 
        
        
        
        
          Oct
           (2)  | 
        
        
        
        
          Nov
           (5)  | 
        
        
        
        
          Dec
           (28)  | 
        
      
| 2017 | 
          Jan
           (11)  | 
        
        
        
        
          Feb
           (6)  | 
        
        
        
        
          Mar
           (10)  | 
        
        
        
        
          Apr
           (10)  | 
        
        
        
        
          May
           (34)  | 
        
        
        
        
          Jun
           (32)  | 
        
        
        
        
          Jul
           (15)  | 
        
        
        
        
          Aug
           (28)  | 
        
        
        
        
          Sep
           (8)  | 
        
        
        
        
          Oct
           (10)  | 
        
        
        
        
          Nov
           (14)  | 
        
        
        
        
          Dec
           (2)  | 
        
      
| 2018 | 
          Jan
           (8)  | 
        
        
        
        
          Feb
           | 
        
        
        
        
          Mar
           | 
        
        
        
        
          Apr
           | 
        
        
        
        
          May
           | 
        
        
        
        
          Jun
           (5)  | 
        
        
        
        
          Jul
           (7)  | 
        
        
        
        
          Aug
           | 
        
        
        
        
          Sep
           (1)  | 
        
        
        
        
          Oct
           | 
        
        
        
        
          Nov
           (15)  | 
        
        
        
        
          Dec
           | 
        
      
| 2019 | 
          Jan
           | 
        
        
        
        
          Feb
           (7)  | 
        
        
        
        
          Mar
           (2)  | 
        
        
        
        
          Apr
           (2)  | 
        
        
        
        
          May
           (2)  | 
        
        
        
        
          Jun
           (2)  | 
        
        
        
        
          Jul
           (48)  | 
        
        
        
        
          Aug
           (73)  | 
        
        
        
        
          Sep
           (22)  | 
        
        
        
        
          Oct
           (8)  | 
        
        
        
        
          Nov
           (16)  | 
        
        
        
        
          Dec
           (26)  | 
        
      
| 2020 | 
          Jan
           (30)  | 
        
        
        
        
          Feb
           (13)  | 
        
        
        
        
          Mar
           (15)  | 
        
        
        
        
          Apr
           (6)  | 
        
        
        
        
          May
           (1)  | 
        
        
        
        
          Jun
           (3)  | 
        
        
        
        
          Jul
           (12)  | 
        
        
        
        
          Aug
           (18)  | 
        
        
        
        
          Sep
           (18)  | 
        
        
        
        
          Oct
           (5)  | 
        
        
        
        
          Nov
           (9)  | 
        
        
        
        
          Dec
           (16)  | 
        
      
| 2021 | 
          Jan
           (13)  | 
        
        
        
        
          Feb
           (17)  | 
        
        
        
        
          Mar
           (19)  | 
        
        
        
        
          Apr
           (70)  | 
        
        
        
        
          May
           (43)  | 
        
        
        
        
          Jun
           (27)  | 
        
        
        
        
          Jul
           (18)  | 
        
        
        
        
          Aug
           (15)  | 
        
        
        
        
          Sep
           (16)  | 
        
        
        
        
          Oct
           (37)  | 
        
        
        
        
          Nov
           (38)  | 
        
        
        
        
          Dec
           (11)  | 
        
      
| 2022 | 
          Jan
           (73)  | 
        
        
        
        
          Feb
           (18)  | 
        
        
        
        
          Mar
           (36)  | 
        
        
        
        
          Apr
           (6)  | 
        
        
        
        
          May
           (8)  | 
        
        
        
        
          Jun
           (33)  | 
        
        
        
        
          Jul
           (22)  | 
        
        
        
        
          Aug
           | 
        
        
        
        
          Sep
           (6)  | 
        
        
        
        
          Oct
           (71)  | 
        
        
        
        
          Nov
           (91)  | 
        
        
        
        
          Dec
           (26)  | 
        
      
| 2023 | 
          Jan
           (12)  | 
        
        
        
        
          Feb
           (5)  | 
        
        
        
        
          Mar
           (5)  | 
        
        
        
        
          Apr
           (34)  | 
        
        
        
        
          May
           (29)  | 
        
        
        
        
          Jun
           (27)  | 
        
        
        
        
          Jul
           (3)  | 
        
        
        
        
          Aug
           (17)  | 
        
        
        
        
          Sep
           (11)  | 
        
        
        
        
          Oct
           (4)  | 
        
        
        
        
          Nov
           (34)  | 
        
        
        
        
          Dec
           (7)  | 
        
      
| 2024 | 
          Jan
           (16)  | 
        
        
        
        
          Feb
           (27)  | 
        
        
        
        
          Mar
           (60)  | 
        
        
        
        
          Apr
           (57)  | 
        
        
        
        
          May
           (55)  | 
        
        
        
        
          Jun
           (50)  | 
        
        
        
        
          Jul
           (36)  | 
        
        
        
        
          Aug
           (108)  | 
        
        
        
        
          Sep
           (27)  | 
        
        
        
        
          Oct
           (33)  | 
        
        
        
        
          Nov
           (15)  | 
        
        
        
        
          Dec
           (14)  | 
        
      
| 2025 | 
          Jan
           (2)  | 
        
        
        
        
          Feb
           (7)  | 
        
        
        
        
          Mar
           (49)  | 
        
        
        
        
          Apr
           (51)  | 
        
        
        
        
          May
           (35)  | 
        
        
        
        
          Jun
           (34)  | 
        
        
        
        
          Jul
           (10)  | 
        
        
        
        
          Aug
           (32)  | 
        
        
        
        
          Sep
           (27)  | 
        
        
        
        
          Oct
           (1)  | 
        
        
        
        
          Nov
           (4)  | 
        
        
        
        
          Dec
           | 
        
      
| 
     
      
      
      From: <mi...@us...> - 2025-09-13 16:01:11
      
     
   | 
Revision: 10232
          http://sourceforge.net/p/docutils/code/10232
Author:   milde
Date:     2025-09-13 16:01:09 +0000 (Sat, 13 Sep 2025)
Log Message:
-----------
Documentation amendments.
Announce removement of `memo.section_level` in Docutils 2.0.
Add some missing element descriptions to The Docutils Document Tree.
Revert section level changes in restructuredtext.rst doc. Move section
"Implicite Hyperlink Targets" to the end.
Mention Roles Defined in Standard Definition Files in
"reStructuredText Interpreted Text Roles".
Modified Paths:
--------------
    trunk/docutils/RELEASE-NOTES.rst
    trunk/docutils/docs/ref/doctree.rst
    trunk/docutils/docs/ref/rst/definitions.rst
    trunk/docutils/docs/ref/rst/restructuredtext.rst
    trunk/docutils/docs/ref/rst/roles.rst
    trunk/docutils/docs/user/rst/cheatsheet.rst
    trunk/web/rst.rst
Modified: trunk/docutils/RELEASE-NOTES.rst
===================================================================
--- trunk/docutils/RELEASE-NOTES.rst	2025-09-11 14:16:33 UTC (rev 10231)
+++ trunk/docutils/RELEASE-NOTES.rst	2025-09-13 16:01:09 UTC (rev 10232)
@@ -210,6 +210,7 @@
 
 * Remove `states.RSTStateMachine.memo.reporter`,
   `states.RSTStateMachine.memo.section_bubble_up_kludge`,
+  `states.RSTStateMachine.memo.section_level`,
   `states.RSTState.title_inconsistent()`, and `states.Line.eofcheck`
   in Docutils 2.0. Ignored since Docutils 0.22.
 
Modified: trunk/docutils/docs/ref/doctree.rst
===================================================================
--- trunk/docutils/docs/ref/doctree.rst	2025-09-11 14:16:33 UTC (rev 10231)
+++ trunk/docutils/docs/ref/doctree.rst	2025-09-13 16:01:09 UTC (rev 10232)
@@ -3357,18 +3357,50 @@
 <rubric>
 ========
 
-     rubric n. 1. a title, heading, or the like, in a manuscript,
-     book, statute, etc., written or printed in red or otherwise
-     distinguished from the rest of the text. ...
+A <rubric> is an informal heading outside the document's section structure.
 
-     -- Random House Webster's College Dictionary, 1991
+:Category:   `Simple Body Elements`_
+:Analogues:  <rubric> has no direct analogues in common DTDs.
+             It shares some features with the <bridgehead> DocBook_ element.
+:Processing: Rendered similar to a section heading, traditionally highlighted
+             in red (hence the name). Rubrics are not (auto)numbered
+             and not included in the document's table of contents.
+:Parents:    all elements employing `%body.elements`_ or
+             `%structure.model`_ in their content models
+:Children:   text data plus `inline elements`_ (`%text.model`_)
+:Attributes: only the `common attributes`_.
 
-A rubric is like an informal heading that doesn't correspond to the
-document's structure.
+Example
+-------
 
-`To be completed`_.
+The reStructuredText `"rubric" directive`_ can be used to get a
+heading at places where a section is not allowed::
 
+    .. sidebar:: Sidebar Title
 
+       This is a sidebar.  It is for text outside the flow
+       of the main text.
+
+       .. rubric:: A heading inside a sidebar
+
+       Sections are not supported in a sidebar.
+       A rubric can be used to structure the content instead.
+
+Pseudo-XML_ fragment from simple parsing::
+
+    <sidebar>
+        <title>
+            Sidebar Title
+        <paragraph>
+            This is a sidebar.  It is for text outside the flow
+            of the main text.
+        <rubric>
+            A heading inside a sidebar
+        <paragraph>
+            Sections are not supported in a sidebar.
+            A rubric can be used to structure the content instead.
+
+
 <section>
 =========
 
@@ -3618,18 +3650,115 @@
 <substitution_definition>
 =========================
 
-The <substitution_definition> element stores a
-reStructuredText `substitution definition`_.
+The <substitution_definition> element stores content that shall
+replace matching `\<substitution_reference>`_ elements.
 
-`To be completed`_.
+:Category:   `Simple Body Elements`_
+:Analogues:  Substitution definitions are analog to XML’s
+             `internal entities`_ or programming language macros.
+:Processing: A <substitution_definition> is not directly rendered.
+:Parents:    all elements employing `%body.elements`_ or
+             `%structure.model`_ in their content models
+:Children:   text data plus `inline elements`_ (`%text.model`_)
+:Attributes: ltrim_, rtrim_, and the `common attributes`_.
+             The names_ attribute uses the "substitution" namespace_.
 
+Examples
+--------
 
+A reStructuredText `substitution definition`_ allows to include
+complex inline structures within text while keeping the details
+out of the text flow::
+
+    The chemical formula for water is |H2O|.
+
+    .. |H2O| replace:: H\ :sub:`2`\ O
+
+
+Pseudo-XML_ fragment from simple parsing::
+
+    <paragraph>
+        The chemical formula for water is
+        <substitution_reference refname="H2O">
+            H2O
+        .
+    <substitution_definition names="H2O">
+        H
+        <subscript>
+            2
+        O
+
+The `references.Substitutions` transform_ replaces the
+`\<substitution_reference>`_ with the content of the
+<substitution_definition>::
+
+    <paragraph>
+        The chemical formula for water is
+        H
+        <subscript>
+            2
+        O
+        .
+    <substitution_definition names="H2O">
+        H
+        <subscript>
+            2
+        O
+
+A substitution definition can also supply an `\<image>`_ for inline use::
+
+    The |biohazard| symbol must be used on
+    medical waste containers.
+
+    .. |biohazard| image:: biohazard.png
+
+Pseudo-XML_ fragment from simple parsing::
+
+    <paragraph>
+        The
+        <substitution_reference refname="biohazard">
+            biohazard
+         symbol must be used on
+        medical waste containers.
+    <substitution_definition names="biohazard">
+        <image alt="biohazard" uri="biohazard.png">
+
+After running the `references.Substitutions` transform_, we get::
+
+    <paragraph>
+        The
+        <image alt="biohazard" uri="biohazard.png">
+         symbol must be used on
+        medical waste containers.
+    <substitution_definition names="biohazard">
+        <image alt="biohazard" uri="biohazard.png">
+
+For more usage examples, see `Substitution Definitions`_ in the
+`reStructuredText Markup Specification`_.
+
+
 <substitution_reference>
 ========================
 
-`To be completed`_.
+The <substitution_reference> element is a placeholder for the content of
+the matching <substitution_definition>.
 
+:Category:   `Inline Elements`_
+:Analogues:  A <substitution_reference> is analogue to an XML
+             `entity reference`_.
+:Processing: Replaced by the content of a matching
+             `\<substitution_definition>`_.
+:Parents:    all elements employing `%text.model`_ in their content models
+:Children:   text data plus `inline elements`_ (`%text.model`_)
+:Attributes: the `common attributes`_ plus refname_.
+             The refname_ attribute uses the "substitution" namespace_.
 
+Examples
+--------
+
+See `\<substitution_definition>`_.
+
+
 <subtitle>
 ==========
 
@@ -4929,7 +5058,6 @@
 `DocTitle transform`_.  It is typically not part of the rendered document
 but, for example, used as `HTML <title> element`_ and shown in a
 browser's title bar, a user's history or bookmarks, or search results.
-
 Its value may may differ from the *displayed title* which is stored in a
 `\<title>`_ element.
 
@@ -5689,11 +5817,12 @@
 .. _XML: https://developer.mozilla.org/en-US/docs/Web/XML/XML_introduction
 .. _Introducing the Extensible Markup Language (XML):
     http://xml.coverpages.org/xmlIntro.html
+.. _internal entities: https://www.w3.org/TR/xml/#sec-internal-ent
+.. _entity reference: https://www.w3.org/TR/xml/#dt-entref
 .. _external DTD subset: https://www.w3.org/TR/xml11/#sec-external-ent
 .. _XML attribute types: https://www.w3.org/TR/REC-xml/#sec-attribute-types
 .. _One ID per Element Type: https://www.w3.org/TR/REC-xml/#one-id-per-el
 
-
 .. _Docutils: https://docutils.sourceforge.io/
 .. _docutils.nodes:
 .. _nodes.py: ../../docutils/nodes.py
@@ -5771,6 +5900,7 @@
 .. _standalone hyperlinks:  rst/restructuredtext.html#standalone-hyperlinks
 .. _strong emphasis:        rst/restructuredtext.html#strong-emphasis
 .. _substitution definition:
+.. _substitution definitions:
 .. _substitutions:          rst/restructuredtext.html#substitution-definitions
 .. _hyperlink targets:      rst/restructuredtext.html#hyperlink-targets
 .. _transition:             rst/restructuredtext.html#transitions
@@ -5798,6 +5928,7 @@
 .. _"contents" directive:       rst/directives.html#table-of-contents
 .. _"csv-table":                rst/directives.html#csv-table
 .. _"danger" directive:         rst/directives.html#danger
+.. _"date" directive:           rst/directives.html#date
 .. _"epigraph":                 rst/directives.html#epigraph
 .. _"error" directive:          rst/directives.html#error
 .. _"figure" directive:         rst/directives.html#figure
@@ -5816,6 +5947,7 @@
 .. _"parsed-literal" directive: rst/directives.html#parsed-literal
 .. _"pull-quote":               rst/directives.html#pull-quote
 .. _"raw" directive:            rst/directives.html#raw
+.. _"rubric" directive:         rst/directives.html#rubric
 .. _"sectnum" directive:        rst/directives.html#sectnum
 .. _"sidebar" directive:        rst/directives.html#sidebar
 .. _"table" directive:          rst/directives.html#table
Modified: trunk/docutils/docs/ref/rst/definitions.rst
===================================================================
--- trunk/docutils/docs/ref/rst/definitions.rst	2025-09-11 14:16:33 UTC (rev 10231)
+++ trunk/docutils/docs/ref/rst/definitions.rst	2025-09-13 16:01:09 UTC (rev 10232)
@@ -215,6 +215,8 @@
 .. [#attribute-optional] Would gain from support for attributes/arguments
    to inline roles (see TODO_).
 
+New in Docutils 0.22
+
 .. _TODO: https://docutils.sourceforge.io/docs/dev/todo.html
           #acronym-and-abbreviation
 .. _"html5" writer: ../../user/html.html#html5
Modified: trunk/docutils/docs/ref/rst/restructuredtext.rst
===================================================================
--- trunk/docutils/docs/ref/rst/restructuredtext.rst	2025-09-11 14:16:33 UTC (rev 10231)
+++ trunk/docutils/docs/ref/rst/restructuredtext.rst	2025-09-13 16:01:09 UTC (rev 10232)
@@ -1703,7 +1703,7 @@
 .. _footnote:
 
 Footnotes
----------
+`````````
 
 :Doctree elements: `\<footnote>`_, `\<label>`_
 :Config settings:  footnote_references_
@@ -1749,7 +1749,7 @@
 
 
 Numbered Footnotes
-``````````````````
+..................
 
 `Numbered footnotes` use decimal numbers as label. The number may
 be specified in the source or automatically assigned.
@@ -1760,7 +1760,7 @@
     The footnote above can be referred to with 1_ or [1]_.
 
 Auto-Numbered Footnotes
-.......................
+'''''''''''''''''''''''
 
 A number sign (``#``) may be used as the first character of a footnote
 label to request automatic numbering of the footnote or footnote
@@ -1809,7 +1809,7 @@
 
 
 Mixed Manual and Auto-Numbered Footnotes
-........................................
+''''''''''''''''''''''''''''''''''''''''
 
 Manual and automatic footnote numbering may both be used within a
 single document, although the results may not be expected.  Manual
@@ -1833,7 +1833,7 @@
 
 
 Auto-Symbol Footnotes
-`````````````````````
+.....................
 
 An asterisk (``*``) may be used as footnote label to request automatic
 symbol generation for footnotes and footnote references.  The asterisk
@@ -1880,7 +1880,7 @@
 
 
 Citations
----------
+`````````
 
 :Doctree element: `\<citation>`_
 :See also:        `citation references`_
@@ -1902,7 +1902,7 @@
 .. _hyperlink target:
 
 Hyperlink Targets
------------------
+`````````````````
 
 :Doctree element: `\<target>`_
 :See also:        | `hyperlink references`_
@@ -2097,7 +2097,7 @@
 .. _anonymous:
 
 Anonymous Hyperlinks
-````````````````````
+....................
 
 The `World Wide Web Consortium`_ recommends in its `HTML Techniques
 for Web Content Accessibility Guidelines`_ that authors should
@@ -2140,7 +2140,7 @@
 
 
 Directives
-----------
+``````````
 
 :Doctree elements: depend on the directive
 
@@ -2245,7 +2245,7 @@
 
 
 Substitution Definitions
-------------------------
+````````````````````````
 
 :Doctree element: `\<substitution_definition>`_
 :See also:        `substitution references`_
@@ -2405,7 +2405,7 @@
 
 
 Comments
---------
+````````
 
 :Doctree element: `\<comment>`_
 :Config setting:  strip_comments_
@@ -2447,7 +2447,7 @@
             +----------------------+
 
 Empty Comments
-``````````````
+..............
 
 An explicit markup start followed by a blank line and nothing else
 (apart from whitespace) is an "_`empty comment`".  It serves to
@@ -2463,58 +2463,6 @@
       This is a block quote.
 
 
-.. _implicit hyperlink target:
-
-Implicit Hyperlink Targets
-==========================
-
-:Doctree elements: `\<section>`_, `\<target>`_
-
-Implicit hyperlink targets are generated by `section titles`_
-and named hyperlink references with `embedded URIs and aliases`_.
-They may also be generated by extension constructs.
-Implicit hyperlink targets behave identically to `explicit hyperlink
-targets`_ except in case of duplicate reference names.
-
-.. _name conflicts:
-
-Ambiguity due to different objects with the same `reference name`_ is
-avoided by the following procedure:
-
-#. Duplicate external__ or indirect__ hyperlink targets that refer to
-   the same URI or hyperlink reference do not conflict.  One target
-   is invalidated_ and an INFO [#level]_ system message inserted.
-
-   __ `external hyperlink targets`_
-   __ `indirect hyperlink targets`_
-
-#. `Explicit hyperlink targets`_, citations_, `numbered footnotes`_,
-   and directives_ with `"name" option`_
-   override any implicit targets having the same reference name.
-   The implicit hyperlink target is invalidated_,
-   and an INFO [#level]_ system message inserted.
-
-#. Duplicate implicit hyperlink targets are both invalidated_,
-   and INFO [#level]_ system messages inserted.
-   For example, if two or more sections
-   have the same title (such as "Introduction" subsections of a
-   rigidly-structured document), there will be duplicate implicit
-   hyperlink targets.
-
-#. Duplicate `explicit hyperlink targets`_ (or other objects with
-   the same reference name) are both invalidated_, and WARNING [#level]_
-   system messages inserted.
-
-The parser returns a set of *unique* hyperlink targets.  The calling
-software (such as Docutils_) can warn of unresolvable links, giving
-reasons for the messages.
-
-.. [#level] See `Error Handling`__ in PEP 258 for a description of
-   system message levels.
-
-   __ ../../peps/pep-0258.html#error-handling
-
-
 Inline Markup
 =============
 
@@ -3126,6 +3074,58 @@
    RFC3986_.
 
 
+.. _implicit hyperlink target:
+
+Implicit Hyperlink Targets
+==========================
+
+:Doctree elements: `\<section>`_, `\<target>`_
+
+Implicit hyperlink targets are generated by `section titles`_
+and named hyperlink references with `embedded URIs and aliases`_.
+They may also be generated by extension constructs.
+Implicit hyperlink targets behave identically to `explicit hyperlink
+targets`_ except in case of duplicate reference names.
+
+.. _name conflicts:
+
+Ambiguity due to different objects with the same `reference name`_ is
+avoided by the following procedure:
+
+#. Duplicate external__ or indirect__ hyperlink targets that refer to
+   the same URI or hyperlink reference do not conflict.  One target
+   is invalidated_ and an INFO [#level]_ system message inserted.
+
+   __ `external hyperlink targets`_
+   __ `indirect hyperlink targets`_
+
+#. `Explicit hyperlink targets`_, citations_, `numbered footnotes`_,
+   and directives_ with `"name" option`_
+   override any implicit targets having the same reference name.
+   The implicit hyperlink target is invalidated_,
+   and an INFO [#level]_ system message inserted.
+
+#. Duplicate implicit hyperlink targets are both invalidated_,
+   and INFO [#level]_ system messages inserted.
+   For example, if two or more sections
+   have the same title (such as "Introduction" subsections of a
+   rigidly-structured document), there will be duplicate implicit
+   hyperlink targets.
+
+#. Duplicate `explicit hyperlink targets`_ (or other objects with
+   the same reference name) are both invalidated_, and WARNING [#level]_
+   system messages inserted.
+
+The parser returns a set of *unique* hyperlink targets.  The calling
+software (such as Docutils_) can warn of unresolvable links, giving
+reasons for the messages.
+
+.. [#level] See `Error Handling`__ in PEP 258 for a description of
+   system message levels.
+
+   __ ../../peps/pep-0258.html#error-handling
+
+
 Measures and Units
 ==================
 
Modified: trunk/docutils/docs/ref/rst/roles.rst
===================================================================
--- trunk/docutils/docs/ref/rst/roles.rst	2025-09-11 14:16:33 UTC (rev 10231)
+++ trunk/docutils/docs/ref/rst/roles.rst	2025-09-13 16:01:09 UTC (rev 10232)
@@ -376,6 +376,14 @@
    prefix for role names is recommended.
 
 
+Roles Defined in Standard Definition Files
+==========================================
+
+The `reStructuredText Standard Definition Files`_ ``html-roles.txt`` and
+``s5defs.txt`` define additional roles for `semantic inline markup`_
+and for use in `S5/HTML slide shows`_ (colors, font sizes, display options).
+
+
 Custom Roles
 ============
 
@@ -391,6 +399,10 @@
 .. _Interpreted Text: restructuredtext.html#interpreted-text
 .. _hyperlink references: restructuredtext.html#hyperlink-references
 
+.. _reStructuredText Standard Definition Files: definitions.html
+.. _HTML5 inline markup elements: definitions.html#additional-roles-for-html
+.. _S5/HTML slide shows: definitions.html#s5-html-definitions
+
 .. _The Docutils Document Tree: ../doctree.html
 .. _class names: ../doctree.html#classname
 .. _classes attribute: ../doctree.html#classes
Modified: trunk/docutils/docs/user/rst/cheatsheet.rst
===================================================================
--- trunk/docutils/docs/user/rst/cheatsheet.rst	2025-09-11 14:16:33 UTC (rev 10231)
+++ trunk/docutils/docs/user/rst/cheatsheet.rst	2025-09-13 16:01:09 UTC (rev 10232)
@@ -76,7 +76,7 @@
 See <https://docutils.sourceforge.io/docs/ref/rst/directives.html> for full info.
 
 ================  ============================================================
-Directive Name    Description (Docutils version added to, in [brackets])
+Directive Name    Description
 ================  ============================================================
 attention         Specific admonition; also "caution", "danger",
                   "error", "hint", "important", "note", "tip", "warning"
Modified: trunk/web/rst.rst
===================================================================
--- trunk/web/rst.rst	2025-09-11 14:16:33 UTC (rev 10231)
+++ trunk/web/rst.rst	2025-09-13 16:01:09 UTC (rev 10232)
@@ -235,8 +235,8 @@
 
 .. |reStructuredText| image:: rst.png
 
+.. Emacs settings
 
-..
    Local Variables:
    mode: indented-text
    indent-tabs-mode: nil
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
 | 
| 
     
      
      
      From: <mi...@us...> - 2025-09-11 14:16:36
      
     
   | 
Revision: 10231
          http://sourceforge.net/p/docutils/code/10231
Author:   milde
Date:     2025-09-11 14:16:33 +0000 (Thu, 11 Sep 2025)
Log Message:
-----------
Small editorial changes.
Add type info, shorten function call, merge repetitive commands.
Modified Paths:
--------------
    trunk/docutils/docutils/parsers/rst/states.py
    trunk/docutils/docutils/writers/latex2e/__init__.py
Modified: trunk/docutils/docutils/parsers/rst/states.py
===================================================================
--- trunk/docutils/docutils/parsers/rst/states.py	2025-09-11 09:29:43 UTC (rev 10230)
+++ trunk/docutils/docutils/parsers/rst/states.py	2025-09-11 14:16:33 UTC (rev 10231)
@@ -229,7 +229,7 @@
     nested_sm = NestedStateMachine
     nested_sm_cache = []
 
-    def __init__(self, state_machine, debug=False) -> None:
+    def __init__(self, state_machine: RSTStateMachine, debug=False) -> None:
         self.nested_sm_kwargs = {'state_classes': state_classes,
                                  'initial_state': 'Body'}
         StateWS.__init__(self, state_machine, debug)
@@ -354,8 +354,8 @@
 
         # run the state machine and populate `node`:
         block_length = len(block)
-        my_state_machine.run(block, input_offset, memo=self.memo,
-                             node=node, match_titles=match_titles)
+        my_state_machine.run(block, input_offset, self.memo,
+                             node, match_titles)
 
         if match_titles:
             if node == self.state_machine.node:
Modified: trunk/docutils/docutils/writers/latex2e/__init__.py
===================================================================
--- trunk/docutils/docutils/writers/latex2e/__init__.py	2025-09-11 09:29:43 UTC (rev 10230)
+++ trunk/docutils/docutils/writers/latex2e/__init__.py	2025-09-11 14:16:33 UTC (rev 10231)
@@ -2761,8 +2761,7 @@
                      *self.ids_to_labels(node, set_anchor=False, newline=True),
                      f'\\end{{{math_env}}}']
         if node['classes']:
-            self.out.append('\n')
-            self.out.append('}' * len(node['classes']))
+            self.out.append('\n' + '}' * len(node['classes']))
         raise nodes.SkipNode  # content already processed
 
     def depart_math_block(self, node) -> None:
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
 | 
| 
     
      
      
      From: <gr...@us...> - 2025-09-11 09:29:45
      
     
   | 
Revision: 10230
          http://sourceforge.net/p/docutils/code/10230
Author:   grubert
Date:     2025-09-11 09:29:43 +0000 (Thu, 11 Sep 2025)
Log Message:
-----------
Fix release date
Modified Paths:
--------------
    trunk/docutils/HISTORY.rst
Modified: trunk/docutils/HISTORY.rst
===================================================================
--- trunk/docutils/HISTORY.rst	2025-09-09 09:54:18 UTC (rev 10229)
+++ trunk/docutils/HISTORY.rst	2025-09-11 09:29:43 UTC (rev 10230)
@@ -50,7 +50,7 @@
   - Simplify code for images nested in reference or figure elements.
 
 
-Release 0.22 (2026-07-29)
+Release 0.22 (2025-07-29)
 =========================
 
 * General
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
 | 
| 
     
      
      
      From: <mi...@us...> - 2025-09-09 09:54:22
      
     
   | 
Revision: 10229
          http://sourceforge.net/p/docutils/code/10229
Author:   milde
Date:     2025-09-09 09:54:18 +0000 (Tue, 09 Sep 2025)
Log Message:
-----------
rST parser: Use `section_level_offset` instead of `memo.section_level`.
Keeping record of the current section level in the state machine's "memo"
is cumbersome and error prone because it needs to be updated with every switch
of the current node.
Store the difference between the intended start level of nested parsing and the
number of parents of the base node in the new attribute `section_level_offset`.
Use it to correct the section level determined via `node.section_hierarchy()`.
Modified Paths:
--------------
    trunk/docutils/docutils/parsers/rst/states.py
    trunk/docutils/test/test_parsers/test_rst/test_nested_parsing.py
Modified: trunk/docutils/docutils/parsers/rst/states.py
===================================================================
--- trunk/docutils/docutils/parsers/rst/states.py	2025-09-08 13:44:23 UTC (rev 10228)
+++ trunk/docutils/docutils/parsers/rst/states.py	2025-09-09 09:54:18 UTC (rev 10229)
@@ -141,7 +141,16 @@
 
     The entry point to reStructuredText parsing is the `run()` method.
     """
+    section_level_offset: int = 0
+    """Correction term for section level determination in nested parsing.
 
+    Updated by `RSTState.nested_parse()` and used in
+    `RSTState.check_subsection()` to compensate differences when
+    nested parsing uses a detached base node with a document-wide
+    section title style hierarchy or the current node with a new,
+    independent title style hierarchy.
+    """
+
     def run(self, input_lines, document, input_offset=0, match_titles=True,
             inliner=None) -> None:
         """
@@ -157,13 +166,13 @@
             inliner = Inliner()
         inliner.init_customizations(document.settings)
         # A collection of objects to share with nested parsers.
-        # The attributes `reporter` and `section_bubble_up_kludge`
-        # will be removed in Docutils 2.0
+        # The attributes `reporter`, `section_level`, and
+        # `section_bubble_up_kludge` will be removed in Docutils 2.0
         self.memo = Struct(document=document,
                            reporter=document.reporter,  # ignored
                            language=self.language,
                            title_styles=[],
-                           section_level=0,  # (0 document, 1 section, ...)
+                           section_level=0,  # ignored
                            section_bubble_up_kludge=False,  # ignored
                            inliner=inliner)
         self.document = document
@@ -176,7 +185,7 @@
         self.node = self.memo = None    # remove unneeded references
 
 
-class NestedStateMachine(StateMachineWS):
+class NestedStateMachine(RSTStateMachine):
     """
     StateMachine run from within other StateMachine runs, to parse nested
     document structures.
@@ -333,6 +342,15 @@
         if (node == self.state_machine.node
                 and not isinstance(node, (nodes.document, nodes.section))):
             match_titles = False  # avoid invalid sections
+        if match_titles:
+            # Compensate mismatch of known title styles and number of
+            # parent sections of the base node if the document wide
+            # title styles are used with a detached base node or
+            # a new list of title styles with the current parent node:
+            l_node = len(node.section_hierarchy())
+            l_start = min(len(self.parent.section_hierarchy()),
+                          len(self.memo.title_styles))
+            my_state_machine.section_level_offset = l_start - l_node
 
         # run the state machine and populate `node`:
         block_length = len(block)
@@ -349,10 +367,6 @@
                         sm = sm.parent_state_machine
                 except AttributeError:
                     pass
-            # set section level
-            # (fails with Sphinx's `_fresh_title_style_context`)
-            self.memo.section_level = len(
-                self.state_machine.node.section_hierarchy())
         # clean up
         new_offset = my_state_machine.abs_line_offset()
         if use_default == 2:
@@ -423,8 +437,10 @@
         (or the root node if the new section is a top-level section).
         """
         title_styles = self.memo.title_styles
+        parent_sections = self.parent.section_hierarchy()
         # current section level: (0 root, 1 section, 2 subsection, ...)
-        oldlevel = self.memo.section_level
+        oldlevel = (len(parent_sections)
+                    + self.state_machine.section_level_offset)
         # new section level:
         try:  # check for existing title style
             newlevel = title_styles.index(style) + 1
@@ -443,7 +459,6 @@
             return False
         if newlevel <= oldlevel:
             # new section is sibling or higher up in the section hierarchy
-            parent_sections = self.parent.section_hierarchy()
             try:
                 new_parent = parent_sections[newlevel-oldlevel-1].parent
             except IndexError:
Modified: trunk/docutils/test/test_parsers/test_rst/test_nested_parsing.py
===================================================================
--- trunk/docutils/test/test_parsers/test_rst/test_nested_parsing.py	2025-09-08 13:44:23 UTC (rev 10228)
+++ trunk/docutils/test/test_parsers/test_rst/test_nested_parsing.py	2025-09-09 09:54:18 UTC (rev 10229)
@@ -71,9 +71,6 @@
                 sm.node = self.state_machine.node
         except AttributeError:
             pass
-        # Update section level:
-        self.state_machine.memo.section_level = len(
-            self.state_machine.node.section_hierarchy())
         return []  # node already attached to document
 
 
@@ -126,9 +123,6 @@
         with _fresh_title_style_context(self.state):
             self.state.nested_parse(self.content, self.content_offset,
                                     match_titles=True)
-        # update section level
-        self.state_machine.memo.section_level = len(
-            self.state_machine.node.section_hierarchy())
         return []  # node already attached to document
 
 
@@ -137,7 +131,6 @@
     # copied from sphinx/sphinx/util/parsing.py
     memo = state.memo
     surrounding_title_styles = memo.title_styles
-    surrounding_section_level = memo.section_level
     memo.title_styles = []
     memo.section_level = 0
     try:
@@ -144,7 +137,6 @@
         yield
     finally:
         memo.title_styles = surrounding_title_styles
-        memo.section_level = surrounding_section_level
 
 
 class ParserTestCase(unittest.TestCase):
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
 | 
| 
     
      
      
      From: <mi...@us...> - 2025-09-08 13:44:25
      
     
   | 
Revision: 10228
          http://sourceforge.net/p/docutils/code/10228
Author:   milde
Date:     2025-09-08 13:44:23 +0000 (Mon, 08 Sep 2025)
Log Message:
-----------
rST parser: simplifications, fixes, and improvements
Define the attribute `parent_state_machine` only in
`parsers.rst.state.NestedStateMachine`.
It does not make sense in other state machines.
More detailled error message for inacessible section parents.
Test with a copy of Sphinx's `_fresh_title_style_context`.
Modified Paths:
--------------
    trunk/docutils/HISTORY.rst
    trunk/docutils/docutils/parsers/rst/states.py
    trunk/docutils/docutils/statemachine.py
    trunk/docutils/test/test_parsers/test_rst/test_nested_parsing.py
Modified: trunk/docutils/HISTORY.rst
===================================================================
--- trunk/docutils/HISTORY.rst	2025-09-05 14:07:30 UTC (rev 10227)
+++ trunk/docutils/HISTORY.rst	2025-09-08 13:44:23 UTC (rev 10228)
@@ -14,7 +14,7 @@
 .. contents::
 
 
-Release 0.23b0 (unpublished)
+Release 0.22.1 (unpublished)
 ============================
 
 * docutils/frontend.py, docutils/writers/
@@ -29,19 +29,14 @@
 * docutils/parsers/rst/states.py
 
   - Relax "section title" system messages from SEVERE to ERROR.
-  - Revert to using `document.memo.section_level` to fix behaviour with
-    nested parsing into a detached node (cf. bugs #508 and #509).
-  - Set `parent_state_machine` attribute when creating nested state machines.
-    Use it to update the "current node" of the parent state machines after
-    nested parsing.
+  - Fix behaviour with nested parsing into a detached node
+    (cf. bugs #508 and #509).
+  - New attribute `NestedStateMachine.parent_state_machine`.
+    Use case: update the "current node" of parent state machine(s)
+    after nested parsing.
   - Better error messages for grid table markup errors (bug #504),
     based on patch #214 by Jynn Nelson.
 
-* docutils/statemachine.py
-
-  - New attribute `StateMachine.parent_state_machine` to store the
-    parent state machine of nested state machines.
-
 * docutils/transforms/references.py
 
   - Better error reports for hyperlinks with embedded URI or alias.
@@ -52,7 +47,7 @@
     for elements with IDs (fixes bug #503).
   - Fix cross-reference anchor placement in figures, images,
     literal-blocks, tables, and (sub)titles.
-  - Simplify code for nested image.
+  - Simplify code for images nested in reference or figure elements.
 
 
 Release 0.22 (2026-07-29)
Modified: trunk/docutils/docutils/parsers/rst/states.py
===================================================================
--- trunk/docutils/docutils/parsers/rst/states.py	2025-09-05 14:07:30 UTC (rev 10227)
+++ trunk/docutils/docutils/parsers/rst/states.py	2025-09-08 13:44:23 UTC (rev 10228)
@@ -182,6 +182,14 @@
     document structures.
     """
 
+    def __init__(self, state_classes, initial_state,
+                 debug=False, parent_state_machine=None) -> None:
+
+        self.parent_state_machine = parent_state_machine
+        """The instance of the parent state machine."""
+
+        super().__init__(state_classes, initial_state, debug)
+
     def run(self, input_lines, input_offset, memo, node, match_titles=True):
         """
         Parse `input_lines` and populate `node`.
@@ -328,7 +336,6 @@
 
         # run the state machine and populate `node`:
         block_length = len(block)
-        old_section_level = self.memo.section_level
         my_state_machine.run(block, input_offset, memo=self.memo,
                              node=node, match_titles=match_titles)
 
@@ -342,8 +349,10 @@
                         sm = sm.parent_state_machine
                 except AttributeError:
                     pass
-            else:
-                self.memo.section_level = old_section_level
+            # set section level
+            # (fails with Sphinx's `_fresh_title_style_context`)
+            self.memo.section_level = len(
+                self.state_machine.node.section_hierarchy())
         # clean up
         new_offset = my_state_machine.abs_line_offset()
         if use_default == 2:
@@ -438,11 +447,12 @@
             try:
                 new_parent = parent_sections[newlevel-oldlevel-1].parent
             except IndexError:
-                new_parent = None
-            if new_parent is None:
                 styles = ' '.join('/'.join(style) for style in title_styles)
                 details = (f'The parent of level {newlevel} sections cannot'
-                           ' be reached.\nOne reason may be a high level'
+                           ' be reached. The parser is at section level'
+                           f' {oldlevel} but the current node has only'
+                           f' {len(parent_sections)} parent section(s).'
+                           '\nOne reason may be a high level'
                            ' section used in a directive that parses its'
                            ' content into a base node not attached to'
                            ' the document\n(up to Docutils 0.21,'
Modified: trunk/docutils/docutils/statemachine.py
===================================================================
--- trunk/docutils/docutils/statemachine.py	2025-09-05 14:07:30 UTC (rev 10227)
+++ trunk/docutils/docutils/statemachine.py	2025-09-08 13:44:23 UTC (rev 10228)
@@ -130,8 +130,7 @@
     results of processing in a list.
     """
 
-    def __init__(self, state_classes, initial_state,
-                 debug=False, parent_state_machine=None) -> None:
+    def __init__(self, state_classes, initial_state, debug=False) -> None:
         """
         Initialize a `StateMachine` object; add state objects.
 
@@ -140,7 +139,6 @@
         - `state_classes`: a list of `State` (sub)classes.
         - `initial_state`: a string, the class name of the initial state.
         - `debug`: a boolean; produce verbose output if true (nonzero).
-        - `parent_state_machine`: the parent of a nested state machine.
         """
         self.input_lines = None
         """`StringList` of input lines (without newlines).
@@ -158,9 +156,6 @@
         self.debug = debug
         """Debugging mode on/off."""
 
-        self.parent_state_machine = parent_state_machine
-        """The instance of the parent state machine or None."""
-
         self.initial_state = initial_state
         """The name of the initial state (key to `self.states`)."""
 
Modified: trunk/docutils/test/test_parsers/test_rst/test_nested_parsing.py
===================================================================
--- trunk/docutils/test/test_parsers/test_rst/test_nested_parsing.py	2025-09-05 14:07:30 UTC (rev 10227)
+++ trunk/docutils/test/test_parsers/test_rst/test_nested_parsing.py	2025-09-08 13:44:23 UTC (rev 10228)
@@ -24,6 +24,7 @@
 """
 
 from pathlib import Path
+import contextlib
 import sys
 import unittest
 
@@ -87,6 +88,7 @@
 
 class ParseIntoSectionNode(ParseIntoNode):
     # Some 3rd party extensions use a <section> as dummy base node.
+    # cf. https://github.com/sphinx-contrib/autoprogram/blob/master/sphinxcontrib/autoprogram.py
     #
     # Attention: this directive is flawed:
     # * no check for section validity,
@@ -98,6 +100,53 @@
         return node.children
 
 
+class FreshParseIntoNode(ParseIntoNode):
+    """Nested parsing with support for sections (separate title styles).
+
+    * no check for section validity,
+    * "current" node not updated! -> element order may get lost.
+
+    cf. `sphinx.util.nodes.nested_parse_with_titles()`
+    and `sphinx.util.parsing.nested_parse_to_nodes()`
+    """
+    def run(self):
+        node = nodes.Element()
+        with _fresh_title_style_context(self.state):
+            self.state.nested_parse(self.content, self.content_offset,
+                                    node, match_titles=True)
+        return node.children
+
+
+class FreshParseIntoCurrentNode(ParseIntoNode):
+    # Nested parsing with support for sections (separate title styles)
+    #
+    # Parsing into the current node, `nested_parse()` ensures validity
+    # and updates the "current node".
+    def run(self):
+        with _fresh_title_style_context(self.state):
+            self.state.nested_parse(self.content, self.content_offset,
+                                    match_titles=True)
+        # update section level
+        self.state_machine.memo.section_level = len(
+            self.state_machine.node.section_hierarchy())
+        return []  # node already attached to document
+
+
+@contextlib.contextmanager
+def _fresh_title_style_context(state):
+    # copied from sphinx/sphinx/util/parsing.py
+    memo = state.memo
+    surrounding_title_styles = memo.title_styles
+    surrounding_section_level = memo.section_level
+    memo.title_styles = []
+    memo.section_level = 0
+    try:
+        yield
+    finally:
+        memo.title_styles = surrounding_title_styles
+        memo.section_level = surrounding_section_level
+
+
 class ParserTestCase(unittest.TestCase):
     maxDiff = None
 
@@ -105,6 +154,8 @@
         register_directive('nested', ParseIntoNode)
         register_directive('nested-current', ParseIntoCurrentNode)
         register_directive('nested-section', ParseIntoSectionNode)
+        register_directive('fresh', FreshParseIntoNode)
+        register_directive('fresh-current', FreshParseIntoCurrentNode)
         parser = rst.Parser()
         settings = get_default_settings(rst.Parser)
         settings.warning_stream = ''
@@ -147,6 +198,8 @@
   ***********
   nested2.1
   ---------
+  nested2.2
+  ---------
   inaccessible2
   =============
 
@@ -183,7 +236,10 @@
         <section ids="nested2-1" names="nested2.1">
             <title>
                 nested2.1
-            <system_message level="3" line="20" source="test data" type="ERROR">
+        <section ids="nested2-2" names="nested2.2">
+            <title>
+                nested2.2
+            <system_message level="3" line="22" source="test data" type="ERROR">
                 <paragraph>
                     A level 1 section cannot be used here.
                 <literal_block xml:space="preserve">
@@ -192,13 +248,13 @@
                 <paragraph>
                     Established title styles: = - * ~
                 <paragraph>
-                    The parent of level 1 sections cannot be reached.
+                    The parent of level 1 sections cannot be reached. The parser is at section level 2 but the current node has only 1 parent section(s).
                     One reason may be a high level section used in a directive that parses its content into a base node not attached to the document
                     (up to Docutils 0.21, these sections were silently dropped).
         <section ids="sec2-2" names="sec2.2">
             <title>
                 sec2.2
-            <system_message level="3" line="25" source="test data" type="ERROR">
+            <system_message level="3" line="27" source="test data" type="ERROR">
                 <paragraph>
                     Inconsistent title style: skip from level 2 to 4.
                 <literal_block xml:space="preserve">
@@ -385,7 +441,7 @@
             <paragraph>
                 Established title styles: =
             <paragraph>
-                The parent of level 1 sections cannot be reached.
+                The parent of level 1 sections cannot be reached. The parser is at section level 1 but the current node has only 0 parent section(s).
                 One reason may be a high level section used in a directive that parses its content into a base node not attached to the document
                 (up to Docutils 0.21, these sections were silently dropped).
         <paragraph>
@@ -439,6 +495,132 @@
             Element <block_quote> invalid:
               Child element <section ids="invalid-section-sic" names="invalid\\ section\\ (sic!)"> not allowed at this position.
 """],
+# Nested parsing with new title style hierarchy
+["""\
+sec1
+====
+sec1.1
+------
+.. fresh::
+
+  fresh1.1.1
+  ==========
+  fresh1.1.1.1
+  ~~~~~~~~~~~~~
+
+sec2
+====
+.. fresh::
+
+  fresh2.1
+  ***********
+  New title styles with every directive.
+
+  fresh2.1.1
+  -----------
+  fresh2.1.2
+  -----------
+  fresh2.1.2.1
+  =============
+
+This text belongs into the last nested section (sic!).
+
+sec2.2
+------
+Document-wide title styles unchanged
+
+sec2.2.1
+********
+""",
+"""\
+<document source="test data">
+    <section ids="sec1" names="sec1">
+        <title>
+            sec1
+        <section ids="sec1-1" names="sec1.1">
+            <title>
+                sec1.1
+            <section ids="fresh1-1-1" names="fresh1.1.1">
+                <title>
+                    fresh1.1.1
+                <section ids="fresh1-1-1-1" names="fresh1.1.1.1">
+                    <title>
+                        fresh1.1.1.1
+    <section ids="sec2" names="sec2">
+        <title>
+            sec2
+        <section ids="fresh2-1" names="fresh2.1">
+            <title>
+                fresh2.1
+            <paragraph>
+                New title styles with every directive.
+            <section ids="fresh2-1-1" names="fresh2.1.1">
+                <title>
+                    fresh2.1.1
+            <section ids="fresh2-1-2" names="fresh2.1.2">
+                <title>
+                    fresh2.1.2
+                <section ids="fresh2-1-2-1" names="fresh2.1.2.1">
+                    <title>
+                        fresh2.1.2.1
+        <paragraph>
+            This text belongs into the last nested section (sic!).
+        <section ids="sec2-2" names="sec2.2">
+            <title>
+                sec2.2
+            <paragraph>
+                Document-wide title styles unchanged
+            <section ids="sec2-2-1" names="sec2.2.1">
+                <title>
+                    sec2.2.1
+    <system_message level="2" line="27" source="test data" type="WARNING">
+        <paragraph>
+            Element <section ids="sec2" names="sec2"> invalid:
+              Child element <paragraph> not allowed at this position.
+"""],
+# Nested parsing into current node with new title style hierarchy
+["""\
+sec1
+====
+sec1.1
+------
+.. fresh-current::
+
+  fc1.1.1
+  -------
+  fc1.1.2
+  -------
+  fc1.1.2.1
+  =========
+
+This text belongs into the last nested section.
+
+sec1.2
+------
+""",
+"""\
+<document source="test data">
+    <section ids="sec1" names="sec1">
+        <title>
+            sec1
+        <section ids="sec1-1" names="sec1.1">
+            <title>
+                sec1.1
+            <section ids="fc1-1-1" names="fc1.1.1">
+                <title>
+                    fc1.1.1
+            <section ids="fc1-1-2" names="fc1.1.2">
+                <title>
+                    fc1.1.2
+                <section ids="fc1-1-2-1" names="fc1.1.2.1">
+                    <title>
+                        fc1.1.2.1
+                    <paragraph>
+                        This text belongs into the last nested section.
+        <section ids="sec1-2" names="sec1.2">
+            <title>
+                sec1.2
+"""],
 ]
 
 
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
 | 
| 
     
      
      
      From: <mi...@us...> - 2025-09-05 14:07:33
      
     
   | 
Revision: 10227
          http://sourceforge.net/p/docutils/code/10227
Author:   milde
Date:     2025-09-05 14:07:30 +0000 (Fri, 05 Sep 2025)
Log Message:
-----------
Adjustments for nested_parse().
* Ensure valid output and update the "current node", if the base node is
  the "current node".
  If the base node is the state machine's "current node", we can safely assume
  that it is attached to the document and check if sections are valid. We can
  also update the "current node" of the parent state machine(s) after parsing.
  This prevents invalid or mixed up document trees.
* Make the "current node" the default value for the "node" argument.
  (This allows simple use for content included from external sources.)
* Restore `memo.section_level` after nested parsing into a custom node.
  The calling code must ensure that section_level and current node stay in sync.
* Update unit tests.
Modified Paths:
--------------
    trunk/docutils/HISTORY.rst
    trunk/docutils/docutils/nodes.py
    trunk/docutils/docutils/parsers/rst/states.py
    trunk/docutils/test/test_nodes.py
    trunk/docutils/test/test_parsers/test_rst/test_nested_parsing.py
Modified: trunk/docutils/HISTORY.rst
===================================================================
--- trunk/docutils/HISTORY.rst	2025-09-05 09:10:58 UTC (rev 10226)
+++ trunk/docutils/HISTORY.rst	2025-09-05 14:07:30 UTC (rev 10227)
@@ -32,8 +32,8 @@
   - Revert to using `document.memo.section_level` to fix behaviour with
     nested parsing into a detached node (cf. bugs #508 and #509).
   - Set `parent_state_machine` attribute when creating nested state machines.
-    Allows passing an updated "current node" to the parent state machine,
-    e.g. for changing the section level in a directive.
+    Use it to update the "current node" of the parent state machines after
+    nested parsing.
   - Better error messages for grid table markup errors (bug #504),
     based on patch #214 by Jynn Nelson.
 
Modified: trunk/docutils/docutils/nodes.py
===================================================================
--- trunk/docutils/docutils/nodes.py	2025-09-05 09:10:58 UTC (rev 10226)
+++ trunk/docutils/docutils/nodes.py	2025-09-05 14:07:30 UTC (rev 10227)
@@ -818,7 +818,7 @@
         return self.parent[i-1] if i > 0 else None
 
     def section_hierarchy(self) -> list[section]:
-        """Return the element's section hierarchy.
+        """Return the element's section anchestors.
 
         Return a list of all <section> elements that contain `self`
         (including `self` if it is a <section>) and have a parent node.
Modified: trunk/docutils/docutils/parsers/rst/states.py
===================================================================
--- trunk/docutils/docutils/parsers/rst/states.py	2025-09-05 09:10:58 UTC (rev 10226)
+++ trunk/docutils/docutils/parsers/rst/states.py	2025-09-05 14:07:30 UTC (rev 10227)
@@ -265,7 +265,7 @@
     def nested_parse(self,
                      block: StringList,
                      input_offset: int,
-                     node: nodes.Element,
+                     node: nodes.Element|None = None,
                      match_titles: bool = False,
                      state_machine_class: StateMachineWS|None = None,
                      state_machine_kwargs: dict|None = None
@@ -279,9 +279,11 @@
             Line number at start of the block.
         :node:
             Base node. Generated nodes will be appended to this node.
+            Default: the "current node" (`self.state_machine.node`).
         :match_titles:
             Allow section titles?
-            Caution: May lead to an invalid or mixed up document tree. [#]_
+            Caution: With a custom base node, this may lead to an invalid
+            or mixed up document tree. [#]_
         :state_machine_class:
             Default: `NestedStateMachine`.
         :state_machine_kwargs:
@@ -297,6 +299,8 @@
         __ https://www.sphinx-doc.org/en/master/extdev/utils.html
            #sphinx.util.parsing.nested_parse_to_nodes
         """
+        if node is None:
+            node = self.state_machine.node
         use_default = 0
         if state_machine_class is None:
             state_machine_class = self.nested_sm
@@ -316,10 +320,30 @@
                                   debug=self.debug,
                                   parent_state_machine=self.state_machine,
                                   **state_machine_kwargs)
+        # Check if we may use sections (with a caveat for custom nodes
+        # that may be dummies to collect children):
+        if (node == self.state_machine.node
+                and not isinstance(node, (nodes.document, nodes.section))):
+            match_titles = False  # avoid invalid sections
+
         # run the state machine and populate `node`:
         block_length = len(block)
+        old_section_level = self.memo.section_level
         my_state_machine.run(block, input_offset, memo=self.memo,
                              node=node, match_titles=match_titles)
+
+        if match_titles:
+            if node == self.state_machine.node:
+                # Pass on the new "current node" to parent state machines:
+                sm = self.state_machine
+                try:
+                    while True:
+                        sm.node = my_state_machine.node
+                        sm = sm.parent_state_machine
+                except AttributeError:
+                    pass
+            else:
+                self.memo.section_level = old_section_level
         # clean up
         new_offset = my_state_machine.abs_line_offset()
         if use_default == 2:
@@ -431,10 +455,9 @@
                     line=lineno)
                 return False
             self.parent = new_parent
-        # Update memo:
+            self.memo.section_level = newlevel - 1
         if newlevel > len(title_styles):
             title_styles.append(style)
-        self.memo.section_level = newlevel
         return True
 
     def title_inconsistent(self, sourcetext, lineno):
@@ -458,6 +481,7 @@
         self.document.note_implicit_target(section_node, section_node)
         # Update state:
         self.parent = section_node
+        self.memo.section_level += 1
 
     def paragraph(self, lines, lineno):
         """
Modified: trunk/docutils/test/test_nodes.py
===================================================================
--- trunk/docutils/test/test_nodes.py	2025-09-05 09:10:58 UTC (rev 10226)
+++ trunk/docutils/test/test_nodes.py	2025-09-05 14:07:30 UTC (rev 10227)
@@ -233,6 +233,25 @@
         self.assertEqual(c1.previous_sibling(), None)
         self.assertEqual(c2.previous_sibling(), c1)
 
+    def test_section_hierarchy(self):
+        p = nodes.paragraph()
+        a = nodes.admonition('', p)
+        self.assertEqual(p.section_hierarchy(), [])
+        s2_1 = nodes.section('', a)
+        self.assertEqual(p.section_hierarchy(), [])
+        s2_2 = nodes.section()
+        s1 = nodes.section()
+        s2 = nodes.section('', s2_1, s2_2)
+        self.assertEqual(p.section_hierarchy(), [s2_1])
+        d = utils.new_document('test data')
+        d += [nodes.paragraph(), s1, s2]
+        self.assertEqual(d.section_hierarchy(), [])
+        self.assertEqual(d[0].section_hierarchy(), [])
+        self.assertEqual(s2.section_hierarchy(), [s2])
+        self.assertEqual(s2_1.section_hierarchy(), [s2, s2_1])
+        self.assertEqual(a.section_hierarchy(), [s2, s2_1])
+        self.assertEqual(p.section_hierarchy(), [s2, s2_1])
+
     def test_clear(self):
         element = nodes.Element()
         element += nodes.Element()
Modified: trunk/docutils/test/test_parsers/test_rst/test_nested_parsing.py
===================================================================
--- trunk/docutils/test/test_parsers/test_rst/test_nested_parsing.py	2025-09-05 09:10:58 UTC (rev 10226)
+++ trunk/docutils/test/test_parsers/test_rst/test_nested_parsing.py	2025-09-05 14:07:30 UTC (rev 10227)
@@ -46,17 +46,17 @@
     has_content = True
 
     def run(self):
-        # cf. sphinx.util.parsing.nested_parse_to_nodes()
         node = nodes.Element()
         node.document = self.state.document
-        # support sections (unless we know it is invalid):
+        # Support sections (unless we know it is invalid):
         match_titles = isinstance(self.state_machine.node,
                                   (nodes.document, nodes.section))
-        self.state.nested_parse(self.content, input_offset=0,
+
+        self.state.nested_parse(self.content, input_offset=self.content_offset,
                                 node=node, match_titles=match_titles)
-        # Append and move the "insertion point" to the last nested section.
         self.state_machine.node += node.children
-        # print(self.state_machine, self.state_machine.node[-1].shortrepr())
+
+        # Move the "insertion point" to the last nested section.
         try:
             while isinstance(self.state_machine.node[-1], nodes.section):
                 self.state_machine.node = self.state_machine.node[-1]
@@ -70,16 +70,18 @@
                 sm.node = self.state_machine.node
         except AttributeError:
             pass
+        # Update section level:
+        self.state_machine.memo.section_level = len(
+            self.state_machine.node.section_hierarchy())
         return []  # node already attached to document
 
 
 class ParseIntoCurrentNode(ParseIntoNode):
-    # Attention: this directive is flawed:
-    # * no check for section validity,
-    # * "current" node not updated! -> element order may get lost.
+    # If `node` is the "current node", `nested_parse()` ensures validity
+    # and updates the "current node".
     def run(self):
-        node = self.state_machine.node  # the current "insertion point"
-        self.state.nested_parse(self.content, 0, node, match_titles=True)
+        self.state.nested_parse(self.content, self.content_offset,
+                                match_titles=True)
         return []  # node already attached to document
 
 
@@ -91,7 +93,8 @@
     # * "current" node not updated! -> element order may get lost.
     def run(self):
         node = nodes.section()
-        self.state.nested_parse(self.content, 0, node, match_titles=True)
+        self.state.nested_parse(self.content, self.content_offset,
+                                node, match_titles=True)
         return node.children
 
 
@@ -169,7 +172,7 @@
     <section ids="sec2" names="sec2">
         <title>
             sec2
-        <system_message level="3" line="1" source="test data" type="ERROR">
+        <system_message level="3" line="16" source="test data" type="ERROR">
             <paragraph>
                 Inconsistent title style: skip from level 1 to 3.
             <literal_block xml:space="preserve">
@@ -180,7 +183,7 @@
         <section ids="nested2-1" names="nested2.1">
             <title>
                 nested2.1
-            <system_message level="3" line="5" source="test data" type="ERROR">
+            <system_message level="3" line="20" source="test data" type="ERROR">
                 <paragraph>
                     A level 1 section cannot be used here.
                 <literal_block xml:space="preserve">
@@ -211,7 +214,9 @@
 
   nested1
   *******
-  nested1.1
+  nested2
+  *******
+  nested2.1
   ---------
 
 This paragraph belongs to the last nested section.
@@ -221,9 +226,12 @@
     <section ids="nested1" names="nested1">
         <title>
             nested1
-        <section ids="nested1-1" names="nested1.1">
+    <section ids="nested2" names="nested2">
+        <title>
+            nested2
+        <section ids="nested2-1" names="nested2.1">
             <title>
-                nested1.1
+                nested2.1
             <paragraph>
                 This paragraph belongs to the last nested section.
 """],
@@ -304,9 +312,6 @@
             <section ids="nc1-1-1" names="nc1.1.1">
                 <title>
                     nc1.1.1
-            <section ids="sec2-2" names="sec2.2">
-                <title>
-                    sec2.2
         <section ids="nc1-2" names="nc1.2">
             <title>
                 nc1.2
@@ -313,6 +318,9 @@
     <section ids="nc2" names="nc2">
         <title>
             nc2
+        <section ids="sec2-2" names="sec2.2">
+            <title>
+                sec2.2
 """],
 # Flawed directive (no update of "current node"):
 ["""\
@@ -326,6 +334,9 @@
   *******************
 
 This paragraph belongs to the last nested section (sic!).
+
+sec2
+====
 """,
 """\
 <document source="test data">
@@ -340,6 +351,9 @@
                     nested-section1.1.1
             <paragraph>
                 This paragraph belongs to the last nested section (sic!).
+    <section ids="sec2" names="sec2">
+        <title>
+            sec2
     <system_message level="2" line="10" source="test data" type="WARNING">
         <paragraph>
             Element <section ids="sec1-1" names="sec1.1"> invalid:
@@ -362,7 +376,7 @@
     <section ids="sec1" names="sec1">
         <title>
             sec1
-        <system_message level="3" line="1" source="test data" type="ERROR">
+        <system_message level="3" line="5" source="test data" type="ERROR">
             <paragraph>
                 A level 1 section cannot be used here.
             <literal_block xml:space="preserve">
@@ -388,8 +402,8 @@
 
   .. nested-current::
 
-    invalid, too (sic!)
-    ===================
+    invalid, too
+    ============
 
   .. nested-section::
 
@@ -409,9 +423,12 @@
             <literal_block xml:space="preserve">
                 invalid section
                 ---------------
-        <section ids="invalid-too-sic" names="invalid,\\ too\\ (sic!)">
-            <title>
-                invalid, too (sic!)
+        <system_message level="3" line="11" source="test data" type="ERROR">
+            <paragraph>
+                Unexpected section title.
+            <literal_block xml:space="preserve">
+                invalid, too
+                ============
         <paragraph>
             The <section> base node is discarded.
         <section ids="invalid-section-sic" names="invalid\\ section\\ (sic!)">
@@ -420,7 +437,7 @@
     <system_message level="2" line="1" source="test data" type="WARNING">
         <paragraph>
             Element <block_quote> invalid:
-              Child element <section ids="invalid-too-sic" names="invalid,\\ too\\ (sic!)"> not allowed at this position.
+              Child element <section ids="invalid-section-sic" names="invalid\\ section\\ (sic!)"> not allowed at this position.
 """],
 ]
 
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
 | 
| 
     
      
      
      From: <mi...@us...> - 2025-09-05 09:11:00
      
     
   | 
Revision: 10226
          http://sourceforge.net/p/docutils/code/10226
Author:   milde
Date:     2025-09-05 09:10:58 +0000 (Fri, 05 Sep 2025)
Log Message:
-----------
rST parser: restore backwards compatibility of nested parsing.
* Keep document-wide title style hierarchy in nested parsing.
* Revert to using `document.memo.section_level` (required for nested parsing
  into a detached base node with document-wide title styles).
+ simpler logic
+ backwards compatible
- more bookkeeping effort
- mandating a document-wide section title style hierarchy is ill-suited for
  inclusion of rST blocks from external sources (e.g. extracted docstrings).
  See `sphinx.util.parsing.nested_parse_to_nodes()` for an alternative.
This should restore compatibility with Sphinx's "only" directive broken by [r10204].
Modified Paths:
--------------
    trunk/docutils/HISTORY.rst
    trunk/docutils/RELEASE-NOTES.rst
    trunk/docutils/docutils/parsers/rst/states.py
    trunk/docutils/test/test_parsers/test_rst/test_nested_parsing.py
Modified: trunk/docutils/HISTORY.rst
===================================================================
--- trunk/docutils/HISTORY.rst	2025-08-28 22:07:49 UTC (rev 10225)
+++ trunk/docutils/HISTORY.rst	2025-09-05 09:10:58 UTC (rev 10226)
@@ -29,9 +29,8 @@
 * docutils/parsers/rst/states.py
 
   - Relax "section title" system messages from SEVERE to ERROR.
-  - Ensure new "current node" is valid when switching section level
-    (cf. bugs #508 and #509).
-  - Use a `separate title style hierarchy for nested parsing`__.
+  - Revert to using `document.memo.section_level` to fix behaviour with
+    nested parsing into a detached node (cf. bugs #508 and #509).
   - Set `parent_state_machine` attribute when creating nested state machines.
     Allows passing an updated "current node" to the parent state machine,
     e.g. for changing the section level in a directive.
@@ -38,8 +37,6 @@
   - Better error messages for grid table markup errors (bug #504),
     based on patch #214 by Jynn Nelson.
 
-  __ RELEASE-NOTES.html#nested-parsing
-
 * docutils/statemachine.py
 
   - New attribute `StateMachine.parent_state_machine` to store the
Modified: trunk/docutils/RELEASE-NOTES.rst
===================================================================
--- trunk/docutils/RELEASE-NOTES.rst	2025-08-28 22:07:49 UTC (rev 10225)
+++ trunk/docutils/RELEASE-NOTES.rst	2025-09-05 09:10:58 UTC (rev 10226)
@@ -210,9 +210,8 @@
 
 * Remove `states.RSTStateMachine.memo.reporter`,
   `states.RSTStateMachine.memo.section_bubble_up_kludge`,
-  `states.RSTStateMachine.memo.section_level`,
   `states.RSTState.title_inconsistent()`, and `states.Line.eofcheck`
-  in Docutils 2.0. Ignored since Docutils 0.22.1.
+  in Docutils 2.0. Ignored since Docutils 0.22.
 
 * Remove `parsers.rst.states.Struct` (obsoleted by `types.SimpleNamespace`)
   in Docutils 2.0.
@@ -264,31 +263,9 @@
 Release 0.23b0 (unpublished)
 ============================
 
-reStructuredText parser:
-  _`Nested parsing` uses a separate section `title style hierarchy`_ if
-  `states.RSTState.nested_parsing()` is used with ``match_titles=True``.
-  Content included via nested parsing may use section title styles in
-  different order, all sections become sub-sections (or sub-sub-section...)
-  of the current section level. [#]_
-  This ensures that all elements generated by the nested parsing are
-  added to the provided base node (without possible data loss as in
-  Docutils < 0.22).
-
-  No changes are required to document sources that work fine
-  in Docutils <= 0.22.
-
-  .. [#] similar to Sphinx's `sphinx.util.node.nested_parse_with_titles()`
-     and overriding the ``keep_title_context`` argument of
-     `sphinx.util.parsing.nested_parse_to_nodes()`__
-
-     __ https://www.sphinx-doc.org/en/master/extdev/utils.html
-        #sphinx.util.parsing.nested_parse_to_nodes
-
 Bugfixes and improvements (see HISTORY_).
 
-.. _title style hierarchy: docs/ref/rst/restructuredtext.html#title-styles
 
-
 Release 0.22 (2025-07-29)
 =========================
 
Modified: trunk/docutils/docutils/parsers/rst/states.py
===================================================================
--- trunk/docutils/docutils/parsers/rst/states.py	2025-08-28 22:07:49 UTC (rev 10225)
+++ trunk/docutils/docutils/parsers/rst/states.py	2025-09-05 09:10:58 UTC (rev 10226)
@@ -104,7 +104,6 @@
 
 __docformat__ = 'reStructuredText'
 
-import copy
 import re
 from types import FunctionType, MethodType
 from types import SimpleNamespace as Struct
@@ -158,13 +157,13 @@
             inliner = Inliner()
         inliner.init_customizations(document.settings)
         # A collection of objects to share with nested parsers.
-        # The attributes `reporter`, `section_level`, and
-        # `section_bubble_up_kludge` will be removed in Docutils 2.0
+        # The attributes `reporter` and `section_bubble_up_kludge`
+        # will be removed in Docutils 2.0
         self.memo = Struct(document=document,
                            reporter=document.reporter,  # ignored
                            language=self.language,
                            title_styles=[],
-                           section_level=0,  # ignored
+                           section_level=0,  # (0 document, 1 section, ...)
                            section_bubble_up_kludge=False,  # ignored
                            inliner=inliner)
         self.document = document
@@ -187,23 +186,15 @@
         """
         Parse `input_lines` and populate `node`.
 
-        Use a separate "title style hierarchy" (changed in Docutils 0.23).
-
         Extend `StateMachineWS.run()`: set up document-wide data.
         """
         self.match_titles = match_titles
-        self.memo = copy.copy(memo)
+        self.memo = memo
         self.document = memo.document
         self.attach_observer(self.document.note_source)
         self.language = memo.language
         self.reporter = self.document.reporter
         self.node = node
-        if match_titles:
-            # Use a separate section title style hierarchy;
-            # ensure all sections in the `input_lines` are treated as
-            # subsections of the current section by blocking lower
-            # section levels with a style that is impossible in rST:
-            self.memo.title_styles = ['x'] * len(node.section_hierarchy())
         results = StateMachineWS.run(self, input_lines, input_offset)
         assert results == [], ('NestedStateMachine.run() results should be '
                                'empty!')
@@ -287,13 +278,10 @@
         :input_offset:
             Line number at start of the block.
         :node:
-            Base node. All generated nodes will be appended to this node.
+            Base node. Generated nodes will be appended to this node.
         :match_titles:
             Allow section titles?
-            A separate section title style hierarchy is used for the nested
-            parsing (all sections are subsections of the current section).
-            The calling code should check whether sections are valid
-            children of the base node and move them or warn otherwise.
+            Caution: May lead to an invalid or mixed up document tree. [#]_
         :state_machine_class:
             Default: `NestedStateMachine`.
         :state_machine_kwargs:
@@ -302,6 +290,12 @@
 
         Create a new state-machine instance if required.
         Return new offset.
+
+        .. [#] See also ``test_parsers/test_rst/test_nested_parsing.py``
+               and Sphinx's `nested_parse_to_nodes()`__.
+
+        __ https://www.sphinx-doc.org/en/master/extdev/utils.html
+           #sphinx.util.parsing.nested_parse_to_nodes
         """
         use_default = 0
         if state_machine_class is None:
@@ -396,9 +390,8 @@
         (or the root node if the new section is a top-level section).
         """
         title_styles = self.memo.title_styles
-        parent_sections = self.parent.section_hierarchy()
         # current section level: (0 root, 1 section, 2 subsection, ...)
-        oldlevel = len(parent_sections)
+        oldlevel = self.memo.section_level
         # new section level:
         try:  # check for existing title style
             newlevel = title_styles.index(style) + 1
@@ -415,13 +408,33 @@
                 nodes.paragraph('', f'Established title styles: {styles}'),
                 line=lineno)
             return False
-        # Update parent state:
+        if newlevel <= oldlevel:
+            # new section is sibling or higher up in the section hierarchy
+            parent_sections = self.parent.section_hierarchy()
+            try:
+                new_parent = parent_sections[newlevel-oldlevel-1].parent
+            except IndexError:
+                new_parent = None
+            if new_parent is None:
+                styles = ' '.join('/'.join(style) for style in title_styles)
+                details = (f'The parent of level {newlevel} sections cannot'
+                           ' be reached.\nOne reason may be a high level'
+                           ' section used in a directive that parses its'
+                           ' content into a base node not attached to'
+                           ' the document\n(up to Docutils 0.21,'
+                           ' these sections were silently dropped).')
+                self.parent += self.reporter.error(
+                    f'A level {newlevel} section cannot be used here.',
+                    nodes.literal_block('', source),
+                    nodes.paragraph('', f'Established title styles: {styles}'),
+                    nodes.paragraph('', details),
+                    line=lineno)
+                return False
+            self.parent = new_parent
+        # Update memo:
         if newlevel > len(title_styles):
             title_styles.append(style)
         self.memo.section_level = newlevel
-        if newlevel <= oldlevel:
-            # new section is sibling or higher up in the section hierarchy
-            self.parent = parent_sections[newlevel-1].parent
         return True
 
     def title_inconsistent(self, sourcetext, lineno):
Modified: trunk/docutils/test/test_parsers/test_rst/test_nested_parsing.py
===================================================================
--- trunk/docutils/test/test_parsers/test_rst/test_nested_parsing.py	2025-08-28 22:07:49 UTC (rev 10225)
+++ trunk/docutils/test/test_parsers/test_rst/test_nested_parsing.py	2025-09-05 09:10:58 UTC (rev 10226)
@@ -7,16 +7,20 @@
 Tests for nested parsing with support for sections (cf. states.py).
 
 The method states.RSTState.nested_parse() provides the argument `match_titles`.
-However, in Docutils, it is only used with `match_titles=False`.
-None of the standard Docutils directives supports section titles in the
-directive content.  (Directives supporting sections in the content are,
-e.g., defined by the "autodoc" and "kerneldoc" Sphinx extensions.)
+With ``match_titles=True``, sections are supported, the section level is
+determined by the document-wide hierarchy of title styles. [1]_
 
-Up to Docutils 0.22, the section title styles were document-wide enforced and
-sections with current level or higher were silently dropped!
+In Docutils, `nested_parse()` is only used with ``match_titles=False``.
+None of the standard Docutils directives support section titles in the
+directive content.   Up to Docutils 0.22, sections with current level or
+higher were silently dropped!
 
-Sphinx uses the `sphinx.util.parsing._fresh_title_style_context` context
-manager to provide a separate title style hierarchy for nested parsing.
+Directives supporting sections in the content are defined
+by Sphinx extensions, e.g., "autodoc" and "kerneldoc".
+
+.. [1] Sphinx uses the `sphinx.util.parsing._fresh_title_style_context`
+       context manager to provide a separate title style hierarchy for
+       nested parsing.
 """
 
 from pathlib import Path
@@ -42,9 +46,9 @@
     has_content = True
 
     def run(self):
-        # similar to sphinx.util.parsing.nested_parse_to_nodes()
+        # cf. sphinx.util.parsing.nested_parse_to_nodes()
         node = nodes.Element()
-        node.document = self.state.document  # not required
+        node.document = self.state.document
         # support sections (unless we know it is invalid):
         match_titles = isinstance(self.state_machine.node,
                                   (nodes.document, nodes.section))
@@ -58,7 +62,7 @@
                 self.state_machine.node = self.state_machine.node[-1]
         except IndexError:
             pass
-        # pass on the new "current node" to parent state machines
+        # Pass current node to parent state machines:
         sm = self.state_machine
         try:
             while True:
@@ -70,30 +74,25 @@
 
 
 class ParseIntoCurrentNode(ParseIntoNode):
+    # Attention: this directive is flawed:
+    # * no check for section validity,
+    # * "current" node not updated! -> element order may get lost.
     def run(self):
         node = self.state_machine.node  # the current "insertion point"
-        # support sections (unless we know it is invalid):
-        match_titles = isinstance(node, (nodes.document, nodes.section))
-        self.state.nested_parse(self.content, 0, node, match_titles)
+        self.state.nested_parse(self.content, 0, node, match_titles=True)
         return []  # node already attached to document
 
 
 class ParseIntoSectionNode(ParseIntoNode):
+    # Some 3rd party extensions use a <section> as dummy base node.
+    #
+    # Attention: this directive is flawed:
+    # * no check for section validity,
+    # * "current" node not updated! -> element order may get lost.
     def run(self):
-        if not isinstance(self.state_machine.node,
-                          (nodes.document, nodes.section)):
-            msg = self.reporter.error(
-                    'The "nested-section" directive can only be used'
-                    ' where a section is valid.',
-                    nodes.literal_block(self.block_text, self.block_text),
-                    line=self.lineno)
-            return [msg]
-        node = nodes.section('')
-        node.append(nodes.title('', 'generated section'))
-        # In production, also generate and register section name and ID
-        # (cf. rst.states.RSTState.new_subsection()).
+        node = nodes.section()
         self.state.nested_parse(self.content, 0, node, match_titles=True)
-        return [node]
+        return node.children
 
 
 class ParserTestCase(unittest.TestCase):
@@ -124,35 +123,34 @@
 totest = {}
 
 totest['nested_parsing'] = [
-# Start new section hierarchy with every nested parse.
+# The document-wide section hierarchy is employed also in nested parsing.
 ["""\
 sec1
 ====
 sec1.1
 ------
-
 .. nested::
 
-  nested1
-  *******
-  nested1.1
-  =========
+  nested1.1.1
+  ***********
+  nested1.1.1.1
+  ~~~~~~~~~~~~~
 
 sec2
 ====
-The document-wide section title styles are kept.
-
 .. nested::
 
-  nested2
-  =======
+  skipping2.1
+  ***********
   nested2.1
-  *********
+  ---------
+  inaccessible2
+  =============
 
 sec2.2
 ------
-sec2.2.1
-~~~~~~~~
+skipping2.2.1
+~~~~~~~~~~~~~
 """,
 """\
 <document source="test data">
@@ -162,32 +160,52 @@
         <section ids="sec1-1" names="sec1.1">
             <title>
                 sec1.1
-            <section ids="nested1" names="nested1">
+            <section ids="nested1-1-1" names="nested1.1.1">
                 <title>
-                    nested1
-                <section ids="nested1-1" names="nested1.1">
+                    nested1.1.1
+                <section ids="nested1-1-1-1" names="nested1.1.1.1">
                     <title>
-                        nested1.1
+                        nested1.1.1.1
     <section ids="sec2" names="sec2">
         <title>
             sec2
-        <paragraph>
-            The document-wide section title styles are kept.
-        <section ids="nested2" names="nested2">
+        <system_message level="3" line="1" source="test data" type="ERROR">
+            <paragraph>
+                Inconsistent title style: skip from level 1 to 3.
+            <literal_block xml:space="preserve">
+                skipping2.1
+                ***********
+            <paragraph>
+                Established title styles: = - * ~
+        <section ids="nested2-1" names="nested2.1">
             <title>
-                nested2
-            <section ids="nested2-1" names="nested2.1">
-                <title>
-                    nested2.1
+                nested2.1
+            <system_message level="3" line="5" source="test data" type="ERROR">
+                <paragraph>
+                    A level 1 section cannot be used here.
+                <literal_block xml:space="preserve">
+                    inaccessible2
+                    =============
+                <paragraph>
+                    Established title styles: = - * ~
+                <paragraph>
+                    The parent of level 1 sections cannot be reached.
+                    One reason may be a high level section used in a directive that parses its content into a base node not attached to the document
+                    (up to Docutils 0.21, these sections were silently dropped).
         <section ids="sec2-2" names="sec2.2">
             <title>
                 sec2.2
-            <section ids="sec2-2-1" names="sec2.2.1">
-                <title>
-                    sec2.2.1
+            <system_message level="3" line="25" source="test data" type="ERROR">
+                <paragraph>
+                    Inconsistent title style: skip from level 2 to 4.
+                <literal_block xml:space="preserve">
+                    skipping2.2.1
+                    ~~~~~~~~~~~~~
+                <paragraph>
+                    Established title styles: = - * ~
 """],
-# Move "insertion point" if the nested block contains sections to
-# comply with the validity constraints of the "structure model".
+# The `ParseIntoNode` directive updates the "current node" to comply with
+# the validity constraints of the "structure model".
 ["""\
 .. nested::
 
@@ -210,8 +228,7 @@
                 This paragraph belongs to the last nested section.
 """],
 ["""\
-.. note:: A preceding directive must not foil the "insertion point move".
-
+.. note:: The next directive is parsed with "nested_list_parse()".
 .. nested::
 
   nested1
@@ -225,7 +242,7 @@
 <document source="test data">
     <note>
         <paragraph>
-            A preceding directive must not foil the "insertion point move".
+            The next directive is parsed with "nested_list_parse()".
     <section ids="nested1" names="nested1">
         <title>
             nested1
@@ -251,23 +268,27 @@
     <paragraph>
         This paragraph belongs to the document.
 """],
-# base node == current node
+# If the base node is the "current node", it is possible to have lower
+# level sections inside the nested content block.
+# The generated nodes are added to the respective parent sections
+# and not necessarily children of the base node.
 ["""\
 sec1
 ====
 sec1.1
 ------
+.. note:: The next directive is parsed with "nested_list_parse()".
 .. nested-current::
 
-  current1
-  ********
-  current1.1
-  -----------
-  current1.1.1
-  ============
+  nc1.1.1
+  *******
+  nc1.2
+  -----
+  nc2
+  ===
 
-sec1.1.2
-~~~~~~~~
+sec2.2
+------
 """,
 """\
 <document source="test data">
@@ -277,20 +298,23 @@
         <section ids="sec1-1" names="sec1.1">
             <title>
                 sec1.1
-            <section ids="current1" names="current1">
+            <note>
+                <paragraph>
+                    The next directive is parsed with "nested_list_parse()".
+            <section ids="nc1-1-1" names="nc1.1.1">
                 <title>
-                    current1
-                <section ids="current1-1" names="current1.1">
-                    <title>
-                        current1.1
-                    <section ids="current1-1-1" names="current1.1.1">
-                        <title>
-                            current1.1.1
-            <section ids="sec1-1-2" names="sec1.1.2">
+                    nc1.1.1
+            <section ids="sec2-2" names="sec2.2">
                 <title>
-                    sec1.1.2
+                    sec2.2
+        <section ids="nc1-2" names="nc1.2">
+            <title>
+                nc1.2
+    <section ids="nc2" names="nc2">
+        <title>
+            nc2
 """],
-# parse into generated <section> node:
+# Flawed directive (no update of "current node"):
 ["""\
 sec1
 ====
@@ -298,16 +322,10 @@
 ------
 .. nested-section::
 
-  nested-section1
-  ***************
-  nested-section1.1
-  =================
+  nested-section1.1.1
+  *******************
 
-This paragraph belongs to the last nested section.
-
-sec1.1.2
-~~~~~~~~
-
+This paragraph belongs to the last nested section (sic!).
 """,
 """\
 <document source="test data">
@@ -317,67 +335,92 @@
         <section ids="sec1-1" names="sec1.1">
             <title>
                 sec1.1
-            <section>
+            <section ids="nested-section1-1-1" names="nested-section1.1.1">
                 <title>
-                    generated section
-                <section ids="nested-section1" names="nested-section1">
-                    <title>
-                        nested-section1
-                    <section ids="nested-section1-1" names="nested-section1.1">
-                        <title>
-                            nested-section1.1
+                    nested-section1.1.1
             <paragraph>
-                This paragraph belongs to the last nested section.
-            <section ids="sec1-1-2" names="sec1.1.2">
-                <title>
-                    sec1.1.2
-    <system_message level="2" line="12" source="test data" type="WARNING">
+                This paragraph belongs to the last nested section (sic!).
+    <system_message level="2" line="10" source="test data" type="WARNING">
         <paragraph>
             Element <section ids="sec1-1" names="sec1.1"> invalid:
               Child element <paragraph> not allowed at this position.
 """],
+# Even if the base node is a <section>, it does not show up in
+# `node.parent_sections()` because it does not have a parent
+# -> we cannot add a sibling section:
+["""\
+sec1
+====
+.. nested-section::
+
+  nested-section1
+  ===============
+  with content
+""",
+"""\
+<document source="test data">
+    <section ids="sec1" names="sec1">
+        <title>
+            sec1
+        <system_message level="3" line="1" source="test data" type="ERROR">
+            <paragraph>
+                A level 1 section cannot be used here.
+            <literal_block xml:space="preserve">
+                nested-section1
+                ===============
+            <paragraph>
+                Established title styles: =
+            <paragraph>
+                The parent of level 1 sections cannot be reached.
+                One reason may be a high level section used in a directive that parses its content into a base node not attached to the document
+                (up to Docutils 0.21, these sections were silently dropped).
+        <paragraph>
+            with content
+"""],
 # Nested parsing in a block-quote:
 ["""\
-  .. nested-current::
+  .. nested::
 
-    Nested parsing is OK but a section is invalid in a block-quote.
+    A section in a block-quote is invalid.
 
-    nested section
-    ==============
-
-  .. nested::
-
     invalid section
     ---------------
 
+  .. nested-current::
+
+    invalid, too (sic!)
+    ===================
+
   .. nested-section::
 
-    The <section> base node is invalid in a block-quote.
+    The <section> base node is discarded.
+
+    invalid section (sic!)
+    ----------------------
 """,
 """\
 <document source="test data">
     <block_quote>
         <paragraph>
-            Nested parsing is OK but a section is invalid in a block-quote.
+            A section in a block-quote is invalid.
         <system_message level="3" line="6" source="test data" type="ERROR">
             <paragraph>
                 Unexpected section title.
             <literal_block xml:space="preserve">
-                nested section
-                ==============
-        <system_message level="3" line="11" source="test data" type="ERROR">
-            <paragraph>
-                Unexpected section title.
-            <literal_block xml:space="preserve">
                 invalid section
                 ---------------
-        <system_message level="3" line="13" source="test data" type="ERROR">
-            <paragraph>
-                The "nested-section" directive can only be used where a section is valid.
-            <literal_block xml:space="preserve">
-                .. nested-section::
-                \n\
-                  The <section> base node is invalid in a block-quote.
+        <section ids="invalid-too-sic" names="invalid,\\ too\\ (sic!)">
+            <title>
+                invalid, too (sic!)
+        <paragraph>
+            The <section> base node is discarded.
+        <section ids="invalid-section-sic" names="invalid\\ section\\ (sic!)">
+            <title>
+                invalid section (sic!)
+    <system_message level="2" line="1" source="test data" type="WARNING">
+        <paragraph>
+            Element <block_quote> invalid:
+              Child element <section ids="invalid-too-sic" names="invalid,\\ too\\ (sic!)"> not allowed at this position.
 """],
 ]
 
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
 | 
| 
     
      
      
      From: <mi...@us...> - 2025-08-28 22:07:51
      
     
   | 
Revision: 10225
          http://sourceforge.net/p/docutils/code/10225
Author:   milde
Date:     2025-08-28 22:07:49 +0000 (Thu, 28 Aug 2025)
Log Message:
-----------
Deprecate the "match_titles" argument of `states.RSTState.nested_list_parse()`.
`nested_list_parse()` is intended for second and subsequent items of lists
and list-like constructs, it does never match section titles.
The argument value was (mis)used in directive classes to check wheter a
`<topic>` or `<sidebar>` element can be returned.
This check is now replaced by testing for a valid parent node.
Modified Paths:
--------------
    trunk/docutils/RELEASE-NOTES.rst
    trunk/docutils/docutils/parsers/rst/directives/body.py
    trunk/docutils/docutils/parsers/rst/directives/parts.py
    trunk/docutils/docutils/parsers/rst/states.py
Modified: trunk/docutils/RELEASE-NOTES.rst
===================================================================
--- trunk/docutils/RELEASE-NOTES.rst	2025-08-28 22:07:36 UTC (rev 10224)
+++ trunk/docutils/RELEASE-NOTES.rst	2025-08-28 22:07:49 UTC (rev 10225)
@@ -217,6 +217,10 @@
 * Remove `parsers.rst.states.Struct` (obsoleted by `types.SimpleNamespace`)
   in Docutils 2.0.
 
+* Ignore the "match_titles" argument of
+  `parsers.rst.states.RSTState.nested_list_parse()` in Docutils 1.0;
+  remove it in Docutils 2.0.
+
 * Remove `frontend.OptionParser`, `frontend.Option`, `frontend.Values`,
   `frontend.store_multiple()`, and `frontend.read_config_file()` when
   migrating to argparse_ in Docutils 2.0 or later.
Modified: trunk/docutils/docutils/parsers/rst/directives/body.py
===================================================================
--- trunk/docutils/docutils/parsers/rst/directives/body.py	2025-08-28 22:07:36 UTC (rev 10224)
+++ trunk/docutils/docutils/parsers/rst/directives/body.py	2025-08-28 22:07:49 UTC (rev 10225)
@@ -19,9 +19,8 @@
 
 
 class BasePseudoSection(Directive):
+    """Base class for Topic and Sidebar."""
 
-    required_arguments = 1
-    optional_arguments = 0
     final_argument_whitespace = True
     option_spec = {'class': directives.class_option,
                    'name': directives.unchanged}
@@ -31,8 +30,8 @@
     """Node class to be used (must be set in subclasses)."""
 
     def run(self):
-        if not (self.state_machine.match_titles
-                or isinstance(self.state_machine.node, nodes.sidebar)):
+        if not isinstance(self.state_machine.node,
+                          (nodes.document, nodes.section, nodes.sidebar)):
             raise self.error('The "%s" directive may not be used within '
                              'topics or body elements.' % self.name)
         self.assert_has_content()
@@ -64,15 +63,14 @@
 
 class Topic(BasePseudoSection):
 
+    required_arguments = 1
     node_class = nodes.topic
 
 
 class Sidebar(BasePseudoSection):
 
+    optional_arguments = 1
     node_class = nodes.sidebar
-
-    required_arguments = 0
-    optional_arguments = 1
     option_spec = BasePseudoSection.option_spec | {
                       'subtitle': directives.unchanged_required}
 
Modified: trunk/docutils/docutils/parsers/rst/directives/parts.py
===================================================================
--- trunk/docutils/docutils/parsers/rst/directives/parts.py	2025-08-28 22:07:36 UTC (rev 10224)
+++ trunk/docutils/docutils/parsers/rst/directives/parts.py	2025-08-28 22:07:49 UTC (rev 10225)
@@ -43,8 +43,8 @@
                    'class': directives.class_option}
 
     def run(self):
-        if not (self.state_machine.match_titles
-                or isinstance(self.state_machine.node, nodes.sidebar)):
+        if not isinstance(self.state_machine.node,
+                          (nodes.document, nodes.section, nodes.sidebar)):
             raise self.error('The "%s" directive may not be used within '
                              'topics or body elements.' % self.name)
         document = self.state_machine.document
Modified: trunk/docutils/docutils/parsers/rst/states.py
===================================================================
--- trunk/docutils/docutils/parsers/rst/states.py	2025-08-28 22:07:36 UTC (rev 10224)
+++ trunk/docutils/docutils/parsers/rst/states.py	2025-08-28 22:07:49 UTC (rev 10225)
@@ -108,6 +108,7 @@
 import re
 from types import FunctionType, MethodType
 from types import SimpleNamespace as Struct
+import warnings
 
 from docutils import nodes, statemachine, utils
 from docutils import ApplicationError, DataError
@@ -341,7 +342,7 @@
                           blank_finish,
                           blank_finish_state=None,
                           extra_settings={},
-                          match_titles=False,
+                          match_titles=False,  # deprecated, will be removed
                           state_machine_class=None,
                           state_machine_kwargs=None):
         """
@@ -355,6 +356,12 @@
         Return new offset and a boolean indicating whether there was a
         blank final line.
         """
+        if match_titles:
+            warnings.warn('The "match_titles" argument of '
+                          'parsers.rst.states.RSTState.nested_list_parse() '
+                          'will be ignored in Docutils 1.0 '
+                          'and removed in Docutils 2.0.',
+                          PendingDeprecationWarning, stacklevel=2)
         if state_machine_class is None:
             state_machine_class = self.nested_sm
         if state_machine_kwargs is None:
@@ -2435,8 +2442,7 @@
               self.state_machine.input_lines[offset:],
               input_offset=self.state_machine.abs_line_offset() + 1,
               node=self.parent, initial_state='Explicit',
-              blank_finish=blank_finish,
-              match_titles=self.state_machine.match_titles)
+              blank_finish=blank_finish)
         self.goto_line(newline_offset)
         if not blank_finish:
             self.parent += self.unindent_warning('Explicit markup')
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
 | 
| 
     
      
      
      From: <mi...@us...> - 2025-08-28 22:07:39
      
     
   | 
Revision: 10224
          http://sourceforge.net/p/docutils/code/10224
Author:   milde
Date:     2025-08-28 22:07:36 +0000 (Thu, 28 Aug 2025)
Log Message:
-----------
More distict variable name in parsers.rst.states.RSTState.nested_list_parse().
The `RSTState.nested_parse()` method creates/uses a new state machine
that differs from the class instance's `self.state_machine`
(which may be the master state machine or another nested state machine).
Rename the local variable holding the new machine from `state_machine`
to `my_state_machine`.
Modified Paths:
--------------
    trunk/docutils/docutils/parsers/rst/states.py
Modified: trunk/docutils/docutils/parsers/rst/states.py
===================================================================
--- trunk/docutils/docutils/parsers/rst/states.py	2025-08-28 16:15:22 UTC (rev 10223)
+++ trunk/docutils/docutils/parsers/rst/states.py	2025-08-28 22:07:36 UTC (rev 10224)
@@ -309,27 +309,28 @@
         if state_machine_kwargs is None:
             state_machine_kwargs = self.nested_sm_kwargs
             use_default += 1
-        state_machine = None
+        my_state_machine = None
         if use_default == 2:
             try:
-                state_machine = self.nested_sm_cache.pop()
+                # get cached state machine, prevent others from using it
+                my_state_machine = self.nested_sm_cache.pop()
             except IndexError:
                 pass
-        if not state_machine:
-            state_machine = state_machine_class(
-                                debug=self.debug,
-                                parent_state_machine=self.state_machine,
-                                **state_machine_kwargs)
-        # run the statemachine and populate `node`:
+        if not my_state_machine:
+            my_state_machine = state_machine_class(
+                                  debug=self.debug,
+                                  parent_state_machine=self.state_machine,
+                                  **state_machine_kwargs)
+        # run the state machine and populate `node`:
         block_length = len(block)
-        state_machine.run(block, input_offset, memo=self.memo,
-                          node=node, match_titles=match_titles)
+        my_state_machine.run(block, input_offset, memo=self.memo,
+                             node=node, match_titles=match_titles)
         # clean up
+        new_offset = my_state_machine.abs_line_offset()
         if use_default == 2:
-            self.nested_sm_cache.append(state_machine)
+            self.nested_sm_cache.append(my_state_machine)
         else:
-            state_machine.unlink()
-        new_offset = state_machine.abs_line_offset()
+            my_state_machine.unlink()
         # No `block.parent` implies disconnected -- lines aren't in sync:
         if block.parent and (len(block) - block_length) != 0:
             # Adjustment for block if modified in nested parse:
@@ -359,20 +360,20 @@
         if state_machine_kwargs is None:
             state_machine_kwargs = self.nested_sm_kwargs.copy()
         state_machine_kwargs['initial_state'] = initial_state
-        state_machine = state_machine_class(
-                            debug=self.debug,
-                            parent_state_machine=self.state_machine,
-                            **state_machine_kwargs)
+        my_state_machine = state_machine_class(
+                               debug=self.debug,
+                               parent_state_machine=self.state_machine,
+                               **state_machine_kwargs)
         if blank_finish_state is None:
             blank_finish_state = initial_state
-        state_machine.states[blank_finish_state].blank_finish = blank_finish
+        my_state_machine.states[blank_finish_state].blank_finish = blank_finish
         for key, value in extra_settings.items():
-            setattr(state_machine.states[initial_state], key, value)
-        state_machine.run(block, input_offset, memo=self.memo,
-                          node=node, match_titles=match_titles)
-        blank_finish = state_machine.states[blank_finish_state].blank_finish
-        state_machine.unlink()
-        return state_machine.abs_line_offset(), blank_finish
+            setattr(my_state_machine.states[initial_state], key, value)
+        my_state_machine.run(block, input_offset, memo=self.memo,
+                             node=node, match_titles=match_titles)
+        blank_finish = my_state_machine.states[blank_finish_state].blank_finish
+        my_state_machine.unlink()
+        return my_state_machine.abs_line_offset(), blank_finish
 
     def section(self, title, source, style, lineno, messages) -> None:
         """Check for a valid subsection and create one if it checks out."""
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
 | 
| 
     
      
      
      From: <mi...@us...> - 2025-08-28 16:15:24
      
     
   | 
Revision: 10223
          http://sourceforge.net/p/docutils/code/10223
Author:   milde
Date:     2025-08-28 16:15:22 +0000 (Thu, 28 Aug 2025)
Log Message:
-----------
New attribute to store the parent state machine of nested state machines.
Allows, e.g., passing an updated "current node" to the parent state machine(s)
to fix issues with nested parse with section support (cf. [bugs:#511]).
Modified Paths:
--------------
    trunk/docutils/HISTORY.rst
    trunk/docutils/docutils/parsers/rst/states.py
    trunk/docutils/docutils/statemachine.py
    trunk/docutils/test/test_parsers/test_rst/test_nested_parsing.py
Modified: trunk/docutils/HISTORY.rst
===================================================================
--- trunk/docutils/HISTORY.rst	2025-08-26 08:06:11 UTC (rev 10222)
+++ trunk/docutils/HISTORY.rst	2025-08-28 16:15:22 UTC (rev 10223)
@@ -32,11 +32,19 @@
   - Ensure new "current node" is valid when switching section level
     (cf. bugs #508 and #509).
   - Use a `separate title style hierarchy for nested parsing`__.
+  - Set `parent_state_machine` attribute when creating nested state machines.
+    Allows passing an updated "current node" to the parent state machine,
+    e.g. for changing the section level in a directive.
   - Better error messages for grid table markup errors (bug #504),
     based on patch #214 by Jynn Nelson.
 
   __ RELEASE-NOTES.html#nested-parsing
 
+* docutils/statemachine.py
+
+  - New attribute `StateMachine.parent_state_machine` to store the
+    parent state machine of nested state machines.
+
 * docutils/transforms/references.py
 
   - Better error reports for hyperlinks with embedded URI or alias.
Modified: trunk/docutils/docutils/parsers/rst/states.py
===================================================================
--- trunk/docutils/docutils/parsers/rst/states.py	2025-08-26 08:06:11 UTC (rev 10222)
+++ trunk/docutils/docutils/parsers/rst/states.py	2025-08-28 16:15:22 UTC (rev 10223)
@@ -316,8 +316,10 @@
             except IndexError:
                 pass
         if not state_machine:
-            state_machine = state_machine_class(debug=self.debug,
-                                                **state_machine_kwargs)
+            state_machine = state_machine_class(
+                                debug=self.debug,
+                                parent_state_machine=self.state_machine,
+                                **state_machine_kwargs)
         # run the statemachine and populate `node`:
         block_length = len(block)
         state_machine.run(block, input_offset, memo=self.memo,
@@ -357,8 +359,10 @@
         if state_machine_kwargs is None:
             state_machine_kwargs = self.nested_sm_kwargs.copy()
         state_machine_kwargs['initial_state'] = initial_state
-        state_machine = state_machine_class(debug=self.debug,
-                                            **state_machine_kwargs)
+        state_machine = state_machine_class(
+                            debug=self.debug,
+                            parent_state_machine=self.state_machine,
+                            **state_machine_kwargs)
         if blank_finish_state is None:
             blank_finish_state = initial_state
         state_machine.states[blank_finish_state].blank_finish = blank_finish
Modified: trunk/docutils/docutils/statemachine.py
===================================================================
--- trunk/docutils/docutils/statemachine.py	2025-08-26 08:06:11 UTC (rev 10222)
+++ trunk/docutils/docutils/statemachine.py	2025-08-28 16:15:22 UTC (rev 10223)
@@ -130,7 +130,8 @@
     results of processing in a list.
     """
 
-    def __init__(self, state_classes, initial_state, debug=False) -> None:
+    def __init__(self, state_classes, initial_state,
+                 debug=False, parent_state_machine=None) -> None:
         """
         Initialize a `StateMachine` object; add state objects.
 
@@ -139,8 +140,8 @@
         - `state_classes`: a list of `State` (sub)classes.
         - `initial_state`: a string, the class name of the initial state.
         - `debug`: a boolean; produce verbose output if true (nonzero).
+        - `parent_state_machine`: the parent of a nested state machine.
         """
-
         self.input_lines = None
         """`StringList` of input lines (without newlines).
         Filled by `self.run()`."""
@@ -157,6 +158,9 @@
         self.debug = debug
         """Debugging mode on/off."""
 
+        self.parent_state_machine = parent_state_machine
+        """The instance of the parent state machine or None."""
+
         self.initial_state = initial_state
         """The name of the initial state (key to `self.states`)."""
 
Modified: trunk/docutils/test/test_parsers/test_rst/test_nested_parsing.py
===================================================================
--- trunk/docutils/test/test_parsers/test_rst/test_nested_parsing.py	2025-08-26 08:06:11 UTC (rev 10222)
+++ trunk/docutils/test/test_parsers/test_rst/test_nested_parsing.py	2025-08-28 16:15:22 UTC (rev 10223)
@@ -51,7 +51,6 @@
         self.state.nested_parse(self.content, input_offset=0,
                                 node=node, match_titles=match_titles)
         # Append and move the "insertion point" to the last nested section.
-        # TODO: this fails in some cases, see tests below.
         self.state_machine.node += node.children
         # print(self.state_machine, self.state_machine.node[-1].shortrepr())
         try:
@@ -59,6 +58,14 @@
                 self.state_machine.node = self.state_machine.node[-1]
         except IndexError:
             pass
+        # pass on the new "current node" to parent state machines
+        sm = self.state_machine
+        try:
+            while True:
+                sm = sm.parent_state_machine
+                sm.node = self.state_machine.node
+        except AttributeError:
+            pass
         return []  # node already attached to document
 
 
@@ -203,7 +210,7 @@
                 This paragraph belongs to the last nested section.
 """],
 ["""\
-.. note:: A preceding directive foils the "insertion point move".
+.. note:: A preceding directive must not foil the "insertion point move".
 
 .. nested::
 
@@ -212,13 +219,13 @@
   nested1.1
   ---------
 
-TODO: This paragraph belongs to the last nested section.
+This paragraph belongs to the last nested section.
 """,
 """\
 <document source="test data">
     <note>
         <paragraph>
-            A preceding directive foils the "insertion point move".
+            A preceding directive must not foil the "insertion point move".
     <section ids="nested1" names="nested1">
         <title>
             nested1
@@ -225,12 +232,24 @@
         <section ids="nested1-1" names="nested1.1">
             <title>
                 nested1.1
+            <paragraph>
+                This paragraph belongs to the last nested section.
+"""],
+["""\
+.. nested::
+
+  Keep the "current node", if the nested parse does not
+  contain a section.
+
+This paragraph belongs to the document.
+""",
+"""\
+<document source="test data">
     <paragraph>
-        TODO: This paragraph belongs to the last nested section.
-    <system_message level="2" line="10" source="test data" type="WARNING">
-        <paragraph>
-            Element <document source="test data"> invalid:
-              Child element <paragraph> not allowed at this position.
+        Keep the "current node", if the nested parse does not
+        contain a section.
+    <paragraph>
+        This paragraph belongs to the document.
 """],
 # base node == current node
 ["""\
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
 | 
| 
     
      
      
      From: <mi...@us...> - 2025-08-26 08:06:14
      
     
   | 
Revision: 10222
          http://sourceforge.net/p/docutils/code/10222
Author:   milde
Date:     2025-08-26 08:06:11 +0000 (Tue, 26 Aug 2025)
Log Message:
-----------
Element after a section from nested parsing may be invalid.
`parsers.rst.RSTSTate.nested_parse()` with `match_titles=True`
(i.e. support for sections) leads to an invalid document tree,
if the nested block contains a section but the element following
the nested block is not a section.
The "structure model" ony allows a `<section>` as sibling after a `<section>`.
https://docutils.sourceforge.io/docs/ref/doctree.html#structure-model
An invalid doctree can be prevented if the following content
is appended to the last nested section instead of its parent.
The "nested" directive attempts this but fails if it is called from
another nested state machine:
In a nested state_machine we cannot access/change the `node` attribute
("insertion point") of the calling state_machine.
Modified Paths:
--------------
    trunk/docutils/test/test_parsers/test_rst/test_nested_parsing.py
Modified: trunk/docutils/test/test_parsers/test_rst/test_nested_parsing.py
===================================================================
--- trunk/docutils/test/test_parsers/test_rst/test_nested_parsing.py	2025-08-25 08:33:52 UTC (rev 10221)
+++ trunk/docutils/test/test_parsers/test_rst/test_nested_parsing.py	2025-08-26 08:06:11 UTC (rev 10222)
@@ -31,10 +31,11 @@
 from docutils import nodes
 from docutils.frontend import get_default_settings
 from docutils.parsers import rst
+from docutils.parsers.rst.directives import register_directive
 from docutils.utils import new_document
 
 
-class ParseIntoDetachedNode(rst.Directive):
+class ParseIntoNode(rst.Directive):
     """A directive implementing nested parsing with support for sections.
     """
     final_argument_whitespace = True
@@ -44,26 +45,36 @@
         # similar to sphinx.util.parsing.nested_parse_to_nodes()
         node = nodes.Element()
         node.document = self.state.document  # not required
-        # support sections if it is valid:
-        match_titles = isinstance(self.state.parent, (nodes.document,
-                                                      nodes.section))
+        # support sections (unless we know it is invalid):
+        match_titles = isinstance(self.state_machine.node,
+                                  (nodes.document, nodes.section))
         self.state.nested_parse(self.content, input_offset=0,
                                 node=node, match_titles=match_titles)
-        return node.children
+        # Append and move the "insertion point" to the last nested section.
+        # TODO: this fails in some cases, see tests below.
+        self.state_machine.node += node.children
+        # print(self.state_machine, self.state_machine.node[-1].shortrepr())
+        try:
+            while isinstance(self.state_machine.node[-1], nodes.section):
+                self.state_machine.node = self.state_machine.node[-1]
+        except IndexError:
+            pass
+        return []  # node already attached to document
 
 
-class ParseIntoCurrentNode(ParseIntoDetachedNode):
+class ParseIntoCurrentNode(ParseIntoNode):
     def run(self):
-        node = self.state.parent  # the current "insertion point"
-        # support sections if it is valid:
+        node = self.state_machine.node  # the current "insertion point"
+        # support sections (unless we know it is invalid):
         match_titles = isinstance(node, (nodes.document, nodes.section))
         self.state.nested_parse(self.content, 0, node, match_titles)
-        return []  # nodes already attached to document
+        return []  # node already attached to document
 
 
-class ParseIntoSectionNode(ParseIntoDetachedNode):
+class ParseIntoSectionNode(ParseIntoNode):
     def run(self):
-        if not isinstance(self.state.parent, (nodes.document, nodes.section)):
+        if not isinstance(self.state_machine.node,
+                          (nodes.document, nodes.section)):
             msg = self.reporter.error(
                     'The "nested-section" directive can only be used'
                     ' where a section is valid.',
@@ -82,12 +93,9 @@
     maxDiff = None
 
     def test_parser(self):
-        rst.directives.register_directive('nested-detached',
-                                          ParseIntoDetachedNode)
-        rst.directives.register_directive('nested-current',
-                                          ParseIntoCurrentNode)
-        rst.directives.register_directive('nested-section',
-                                          ParseIntoSectionNode)
+        register_directive('nested', ParseIntoNode)
+        register_directive('nested-current', ParseIntoCurrentNode)
+        register_directive('nested-section', ParseIntoSectionNode)
         parser = rst.Parser()
         settings = get_default_settings(rst.Parser)
         settings.warning_stream = ''
@@ -97,6 +105,11 @@
                 with self.subTest(id=f'totest[{name!r}][{casenum}]'):
                     document = new_document('test data', settings.copy())
                     parser.parse(case_input, document)
+                    try:
+                        document.validate()
+                    except nodes.ValidationError as e:
+                        document.append(document.reporter.warning(
+                            str(e), base_node=e.problematic_element or None))
                     output = document.pformat()
                     self.assertEqual(case_expected, output)
 
@@ -104,57 +117,35 @@
 totest = {}
 
 totest['nested_parsing'] = [
-
+# Start new section hierarchy with every nested parse.
 ["""\
-Parse into section node:
-
-.. nested-section::
-
-  This is nested.
-
-sec2
-====
-""",
-"""\
-<document source="test data">
-    <paragraph>
-        Parse into section node:
-    <section>
-        <title>
-            generated section
-        <paragraph>
-            This is nested.
-    <section ids="sec2" names="sec2">
-        <title>
-            sec2
-"""],
-# start new section hierarchy with every nested parse
-["""\
 sec1
 ====
 sec1.1
 ------
-.. nested-detached::
 
-  detached1
-  *********
-  detached1.1
-  -----------
-  detached1.1.1
-  =============
+.. nested::
 
-.. nested-detached::
+  nested1
+  *******
+  nested1.1
+  =========
 
-  detached2
-  ---------
-  detached2.1
-  ***********
-
-sec1.1.1
-~~~~~~~~
 sec2
 ====
 The document-wide section title styles are kept.
+
+.. nested::
+
+  nested2
+  =======
+  nested2.1
+  *********
+
+sec2.2
+------
+sec2.2.1
+~~~~~~~~
 """,
 """\
 <document source="test data">
@@ -164,30 +155,83 @@
         <section ids="sec1-1" names="sec1.1">
             <title>
                 sec1.1
-            <section ids="detached1" names="detached1">
+            <section ids="nested1" names="nested1">
                 <title>
-                    detached1
-                <section ids="detached1-1" names="detached1.1">
+                    nested1
+                <section ids="nested1-1" names="nested1.1">
                     <title>
-                        detached1.1
-                    <section ids="detached1-1-1" names="detached1.1.1">
-                        <title>
-                            detached1.1.1
-            <section ids="detached2" names="detached2">
-                <title>
-                    detached2
-                <section ids="detached2-1" names="detached2.1">
-                    <title>
-                        detached2.1
-            <section ids="sec1-1-1" names="sec1.1.1">
-                <title>
-                    sec1.1.1
+                        nested1.1
     <section ids="sec2" names="sec2">
         <title>
             sec2
         <paragraph>
             The document-wide section title styles are kept.
+        <section ids="nested2" names="nested2">
+            <title>
+                nested2
+            <section ids="nested2-1" names="nested2.1">
+                <title>
+                    nested2.1
+        <section ids="sec2-2" names="sec2.2">
+            <title>
+                sec2.2
+            <section ids="sec2-2-1" names="sec2.2.1">
+                <title>
+                    sec2.2.1
 """],
+# Move "insertion point" if the nested block contains sections to
+# comply with the validity constraints of the "structure model".
+["""\
+.. nested::
+
+  nested1
+  *******
+  nested1.1
+  ---------
+
+This paragraph belongs to the last nested section.
+""",
+"""\
+<document source="test data">
+    <section ids="nested1" names="nested1">
+        <title>
+            nested1
+        <section ids="nested1-1" names="nested1.1">
+            <title>
+                nested1.1
+            <paragraph>
+                This paragraph belongs to the last nested section.
+"""],
+["""\
+.. note:: A preceding directive foils the "insertion point move".
+
+.. nested::
+
+  nested1
+  *********
+  nested1.1
+  ---------
+
+TODO: This paragraph belongs to the last nested section.
+""",
+"""\
+<document source="test data">
+    <note>
+        <paragraph>
+            A preceding directive foils the "insertion point move".
+    <section ids="nested1" names="nested1">
+        <title>
+            nested1
+        <section ids="nested1-1" names="nested1.1">
+            <title>
+                nested1.1
+    <paragraph>
+        TODO: This paragraph belongs to the last nested section.
+    <system_message level="2" line="10" source="test data" type="WARNING">
+        <paragraph>
+            Element <document source="test data"> invalid:
+              Child element <paragraph> not allowed at this position.
+"""],
 # base node == current node
 ["""\
 sec1
@@ -203,7 +247,7 @@
   current1.1.1
   ============
 
-sec1.1.1
+sec1.1.2
 ~~~~~~~~
 """,
 """\
@@ -223,9 +267,9 @@
                     <section ids="current1-1-1" names="current1.1.1">
                         <title>
                             current1.1.1
-            <section ids="sec1-1-1" names="sec1.1.1">
+            <section ids="sec1-1-2" names="sec1.1.2">
                 <title>
-                    sec1.1.1
+                    sec1.1.2
 """],
 # parse into generated <section> node:
 ["""\
@@ -235,12 +279,14 @@
 ------
 .. nested-section::
 
-  attached1
-  *********
-  attached1.1
-  ===========
+  nested-section1
+  ***************
+  nested-section1.1
+  =================
 
-sec1.1.1
+This paragraph belongs to the last nested section.
+
+sec1.1.2
 ~~~~~~~~
 
 """,
@@ -255,15 +301,21 @@
             <section>
                 <title>
                     generated section
-                <section ids="attached1" names="attached1">
+                <section ids="nested-section1" names="nested-section1">
                     <title>
-                        attached1
-                    <section ids="attached1-1" names="attached1.1">
+                        nested-section1
+                    <section ids="nested-section1-1" names="nested-section1.1">
                         <title>
-                            attached1.1
-            <section ids="sec1-1-1" names="sec1.1.1">
+                            nested-section1.1
+            <paragraph>
+                This paragraph belongs to the last nested section.
+            <section ids="sec1-1-2" names="sec1.1.2">
                 <title>
-                    sec1.1.1
+                    sec1.1.2
+    <system_message level="2" line="12" source="test data" type="WARNING">
+        <paragraph>
+            Element <section ids="sec1-1" names="sec1.1"> invalid:
+              Child element <paragraph> not allowed at this position.
 """],
 # Nested parsing in a block-quote:
 ["""\
@@ -274,7 +326,7 @@
     nested section
     ==============
 
-  .. nested-detached::
+  .. nested::
 
     invalid section
     ---------------
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
 | 
| 
     
      
      
      From: <mi...@us...> - 2025-08-25 08:33:55
      
     
   | 
Revision: 10221
          http://sourceforge.net/p/docutils/code/10221
Author:   milde
Date:     2025-08-25 08:33:52 +0000 (Mon, 25 Aug 2025)
Log Message:
-----------
Use a "property" for `parsers.rst.states.RSTState.parent`.
Using a property attribute instead of runtime initialization ensures
the "insertion point"/"current node" is synchronised across the
statemachine and all state instances.
This may allow support for nested parsing with document-wide section title styles.
Modified Paths:
--------------
    trunk/docutils/docutils/parsers/rst/states.py
Modified: trunk/docutils/docutils/parsers/rst/states.py
===================================================================
--- trunk/docutils/docutils/parsers/rst/states.py	2025-08-22 11:14:54 UTC (rev 10220)
+++ trunk/docutils/docutils/parsers/rst/states.py	2025-08-25 08:33:52 UTC (rev 10221)
@@ -232,11 +232,18 @@
         self.document = memo.document
         self.inliner = memo.inliner
         self.reporter = self.document.reporter
-        self.parent = self.state_machine.node
         # enable the reporter to determine source and source-line
         if not hasattr(self.reporter, 'get_source_and_line'):
             self.reporter.get_source_and_line = self.state_machine.get_source_and_line  # noqa:E501
 
+    @property
+    def parent(self) -> nodes.Element | None:
+        return self.state_machine.node
+
+    @parent.setter
+    def parent(self, value: nodes.Element):
+        self.state_machine.node = value
+
     def goto_line(self, abs_line_offset) -> None:
         """
         Jump to input line `abs_line_offset`, ignoring jumps past the end.
@@ -425,15 +432,7 @@
         section_node += title_messages
         self.document.note_implicit_target(section_node, section_node)
         # Update state:
-        self.state_machine.node = section_node
-        # Also update the ".parent" attribute in all states.
-        # This is a bit violent, but the state classes copy their .parent from
-        # state_machine.node on creation, so we need to update them. We could
-        # also remove RSTState.parent entirely and replace references to it
-        # with statemachine.node, but that might break code downstream of
-        # docutils.
-        for s in self.state_machine.states.values():
-            s.parent = section_node
+        self.parent = section_node
 
     def paragraph(self, lines, lineno):
         """
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
 | 
| 
     
      
      
      From: <mi...@us...> - 2025-08-22 11:14:56
      
     
   | 
Revision: 10220
          http://sourceforge.net/p/docutils/code/10220
Author:   milde
Date:     2025-08-22 11:14:54 +0000 (Fri, 22 Aug 2025)
Log Message:
-----------
Fix/Update tests for nested parsing with sections.
Add error messages for invalid sections to the sample directives.
Update the sample text to mirror the current behaviour.
Modified Paths:
--------------
    trunk/docutils/test/test_parsers/test_rst/test_nested_parsing.py
Modified: trunk/docutils/test/test_parsers/test_rst/test_nested_parsing.py
===================================================================
--- trunk/docutils/test/test_parsers/test_rst/test_nested_parsing.py	2025-08-21 15:18:05 UTC (rev 10219)
+++ trunk/docutils/test/test_parsers/test_rst/test_nested_parsing.py	2025-08-22 11:14:54 UTC (rev 10220)
@@ -43,9 +43,12 @@
     def run(self):
         # similar to sphinx.util.parsing.nested_parse_to_nodes()
         node = nodes.Element()
-        node.document = self.state.document
+        node.document = self.state.document  # not required
+        # support sections if it is valid:
+        match_titles = isinstance(self.state.parent, (nodes.document,
+                                                      nodes.section))
         self.state.nested_parse(self.content, input_offset=0,
-                                node=node, match_titles=True)
+                                node=node, match_titles=match_titles)
         return node.children
 
 
@@ -52,16 +55,27 @@
 class ParseIntoCurrentNode(ParseIntoDetachedNode):
     def run(self):
         node = self.state.parent  # the current "insertion point"
-        self.state.nested_parse(self.content, 0, node, match_titles=True)
+        # support sections if it is valid:
+        match_titles = isinstance(node, (nodes.document, nodes.section))
+        self.state.nested_parse(self.content, 0, node, match_titles)
         return []  # nodes already attached to document
 
 
-class ParseIntoAttachedNode(ParseIntoDetachedNode):
+class ParseIntoSectionNode(ParseIntoDetachedNode):
     def run(self):
-        node = nodes.sidebar('')
-        self.state.parent.append(node)
+        if not isinstance(self.state.parent, (nodes.document, nodes.section)):
+            msg = self.reporter.error(
+                    'The "nested-section" directive can only be used'
+                    ' where a section is valid.',
+                    nodes.literal_block(self.block_text, self.block_text),
+                    line=self.lineno)
+            return [msg]
+        node = nodes.section('')
+        node.append(nodes.title('', 'generated section'))
+        # In production, also generate and register section name and ID
+        # (cf. rst.states.RSTState.new_subsection()).
         self.state.nested_parse(self.content, 0, node, match_titles=True)
-        return []  # nodes already attached to document
+        return [node]
 
 
 class ParserTestCase(unittest.TestCase):
@@ -72,8 +86,8 @@
                                           ParseIntoDetachedNode)
         rst.directives.register_directive('nested-current',
                                           ParseIntoCurrentNode)
-        rst.directives.register_directive('nested-attached',
-                                          ParseIntoAttachedNode)
+        rst.directives.register_directive('nested-section',
+                                          ParseIntoSectionNode)
         parser = rst.Parser()
         settings = get_default_settings(rst.Parser)
         settings.warning_stream = ''
@@ -89,29 +103,32 @@
 
 totest = {}
 
-# Parse into the base node:
 totest['nested_parsing'] = [
+
 ["""\
-Preceding paragraph.
+Parse into section node:
 
-.. nested-attached::
+.. nested-section::
 
-  .. hint:: this is nested.
+  This is nested.
 
-Succeeding paragraph.
+sec2
+====
 """,
 """\
 <document source="test data">
     <paragraph>
-        Preceding paragraph.
-    <sidebar>
-        <hint>
-            <paragraph>
-                this is nested.
-    <paragraph>
-        Succeeding paragraph.
+        Parse into section node:
+    <section>
+        <title>
+            generated section
+        <paragraph>
+            This is nested.
+    <section ids="sec2" names="sec2">
+        <title>
+            sec2
 """],
-# detached base node -> start new section hierarchy with every nested parse
+# start new section hierarchy with every nested parse
 ["""\
 sec1
 ====
@@ -133,8 +150,8 @@
   detached2.1
   ***********
 
-Succeeding paragraph.
-
+sec1.1.1
+~~~~~~~~
 sec2
 ====
 The document-wide section title styles are kept.
@@ -162,8 +179,9 @@
                 <section ids="detached2-1" names="detached2.1">
                     <title>
                         detached2.1
-            <paragraph>
-                Succeeding paragraph.
+            <section ids="sec1-1-1" names="sec1.1.1">
+                <title>
+                    sec1.1.1
     <section ids="sec2" names="sec2">
         <title>
             sec2
@@ -170,7 +188,7 @@
         <paragraph>
             The document-wide section title styles are kept.
 """],
-# base node == current node -> keep section hierarchy
+# base node == current node
 ["""\
 sec1
 ====
@@ -184,9 +202,9 @@
   -----------
   current1.1.1
   ============
-  Top-level section appended to document.
 
-Succeeding paragraph.
+sec1.1.1
+~~~~~~~~
 """,
 """\
 <document source="test data">
@@ -205,18 +223,17 @@
                     <section ids="current1-1-1" names="current1.1.1">
                         <title>
                             current1.1.1
-                        <paragraph>
-                            Top-level section appended to document.
-            <paragraph>
-                Succeeding paragraph.
+            <section ids="sec1-1-1" names="sec1.1.1">
+                <title>
+                    sec1.1.1
 """],
-# parse into attached wrapper node:
+# parse into generated <section> node:
 ["""\
 sec1
 ====
 sec1.1
 ------
-.. nested-attached::
+.. nested-section::
 
   attached1
   *********
@@ -223,7 +240,9 @@
   attached1.1
   ===========
 
-Succeeding paragraph.
+sec1.1.1
+~~~~~~~~
+
 """,
 """\
 <document source="test data">
@@ -233,7 +252,9 @@
         <section ids="sec1-1" names="sec1.1">
             <title>
                 sec1.1
-            <sidebar>
+            <section>
+                <title>
+                    generated section
                 <section ids="attached1" names="attached1">
                     <title>
                         attached1
@@ -240,78 +261,52 @@
                     <section ids="attached1-1" names="attached1.1">
                         <title>
                             attached1.1
-            <paragraph>
-                Succeeding paragraph.
-"""],
-# detached base node -> start new section hierarchy
-["""\
-sec1
-====
-sec1.1
-------
-sec2
-====
-.. nested-detached::
-  detached1
-  ~~~~~~~~~
-  detached1.1
-  -----------
-
-Succeeding paragraph.
-""",
-"""\
-<document source="test data">
-    <section ids="sec1" names="sec1">
-        <title>
-            sec1
-        <section ids="sec1-1" names="sec1.1">
-            <title>
-                sec1.1
-    <section ids="sec2" names="sec2">
-        <title>
-            sec2
-        <section ids="detached1" names="detached1">
-            <title>
-                detached1
-            <section ids="detached1-1" names="detached1.1">
+            <section ids="sec1-1-1" names="sec1.1.1">
                 <title>
-                    detached1.1
-        <paragraph>
-            Succeeding paragraph.
+                    sec1.1.1
 """],
-# base node == <blockquote>
+# Nested parsing in a block-quote:
 ["""\
-sec1
-====
+  .. nested-current::
 
-  A block-quote is parsed into a detached <blockquote> element.
+    Nested parsing is OK but a section is invalid in a block-quote.
 
-  .. nested-current::
-
     nested section
     ==============
 
-  The nested <section> becomes a child of the <blockquote> (sic.)!
+  .. nested-detached::
 
-The calling directive should move the nested <section> or report
-a validity violation.
+    invalid section
+    ---------------
+
+  .. nested-section::
+
+    The <section> base node is invalid in a block-quote.
 """,
 """\
 <document source="test data">
-    <section ids="sec1" names="sec1">
-        <title>
-            sec1
-        <block_quote>
+    <block_quote>
+        <paragraph>
+            Nested parsing is OK but a section is invalid in a block-quote.
+        <system_message level="3" line="6" source="test data" type="ERROR">
             <paragraph>
-                A block-quote is parsed into a detached <blockquote> element.
-            <section ids="nested-section" names="nested\\ section">
-                <title>
-                    nested section
+                Unexpected section title.
+            <literal_block xml:space="preserve">
+                nested section
+                ==============
+        <system_message level="3" line="11" source="test data" type="ERROR">
             <paragraph>
-                The nested <section> becomes a child of the <blockquote> (sic.)!
-        <paragraph>
-            The calling directive should move the nested <section> or report
-            a validity violation.
+                Unexpected section title.
+            <literal_block xml:space="preserve">
+                invalid section
+                ---------------
+        <system_message level="3" line="13" source="test data" type="ERROR">
+            <paragraph>
+                The "nested-section" directive can only be used where a section is valid.
+            <literal_block xml:space="preserve">
+                .. nested-section::
+                \n\
+                  The <section> base node is invalid in a block-quote.
 """],
 ]
 
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
 | 
| 
     
      
      
      From: <mi...@us...> - 2025-08-21 15:18:09
      
     
   | 
Revision: 10219
          http://sourceforge.net/p/docutils/code/10219
Author:   milde
Date:     2025-08-21 15:18:05 +0000 (Thu, 21 Aug 2025)
Log Message:
-----------
LaTeX writer: write "label" commands for elements with "ids".
Fix [bugs:#503].
Currently, internal cross-references to most body elements (admonitions, lists,
block_quotes, container, compound, ...) fail, because the "ids"
attribute of these elements is ignored.
Calls `ids_to_labels()` for (almost) all elements that may have one or more IDs.
TODO:
* test/revise citations and footnotes
* class handling for image and list items
  (use DUclass environment or DUrole function?)
* Currently, there is no way to specify a target name or custom class for
  the "docinfo" in the rST source.
Modified Paths:
--------------
    trunk/docutils/HISTORY.rst
    trunk/docutils/docutils/writers/latex2e/__init__.py
    trunk/docutils/test/functional/expected/latex_cornercases.tex
    trunk/docutils/test/functional/expected/latex_leavevmode.tex
    trunk/docutils/test/functional/expected/latex_memoir.tex
    trunk/docutils/test/functional/expected/standalone_rst_latex.tex
    trunk/docutils/test/functional/expected/standalone_rst_xetex.tex
    trunk/docutils/test/test_writers/test_latex2e.py
    trunk/docutils/test/test_writers/test_latex2e_parts.py
Modified: trunk/docutils/HISTORY.rst
===================================================================
--- trunk/docutils/HISTORY.rst	2025-08-20 12:04:54 UTC (rev 10218)
+++ trunk/docutils/HISTORY.rst	2025-08-21 15:18:05 UTC (rev 10219)
@@ -43,7 +43,8 @@
 
 * docutils/writers/latex2e/__init__.py
 
-  - Prepend ``\phantomsection`` to labelled math-blocks.
+  - Add cross-reference anchors (``\phantomsection\label{...}``)
+    for elements with IDs (fixes bug #503).
   - Fix cross-reference anchor placement in figures, images,
     literal-blocks, tables, and (sub)titles.
   - Simplify code for nested image.
Modified: trunk/docutils/docutils/writers/latex2e/__init__.py
===================================================================
--- trunk/docutils/docutils/writers/latex2e/__init__.py	2025-08-20 12:04:54 UTC (rev 10218)
+++ trunk/docutils/docutils/writers/latex2e/__init__.py	2025-08-21 15:18:05 UTC (rev 10219)
@@ -1714,6 +1714,8 @@
         self.provide_fallback('admonition')
         if 'error' in node['classes']:
             self.provide_fallback('error')
+        if not isinstance(node, nodes.system_message):
+            self.out += self.ids_to_labels(node, pre_nl=True)
         self.duclass_open(node)
         self.out.append('\\begin{DUadmonition}')
 
@@ -1746,6 +1748,7 @@
         self.depart_docinfo_item(node)
 
     def visit_block_quote(self, node) -> None:
+        self.out += self.ids_to_labels(node, pre_nl=True)
         self.duclass_open(node)
         self.out.append('\\begin{quote}')
 
@@ -1754,6 +1757,7 @@
         self.duclass_close(node)
 
     def visit_bullet_list(self, node) -> None:
+        self.out += self.ids_to_labels(node, pre_nl=True)
         self.duclass_open(node)
         self.out.append('\\begin{itemize}')
 
@@ -1876,6 +1880,7 @@
     def visit_compound(self, node) -> None:
         if isinstance(node.parent, nodes.compound):
             self.out.append('\n')
+        self.out += self.ids_to_labels(node, pre_nl=True)
         node['classes'].insert(0, 'compound')
         self.duclass_open(node)
 
@@ -1889,6 +1894,7 @@
         self.depart_docinfo_item(node)
 
     def visit_container(self, node) -> None:
+        self.out += self.ids_to_labels(node, pre_nl=True)
         self.duclass_open(node)
 
     def depart_container(self, node) -> None:
@@ -1920,6 +1926,7 @@
         pass
 
     def visit_definition_list(self, node) -> None:
+        self.out += self.ids_to_labels(node, pre_nl=True)
         self.duclass_open(node)
         self.out.append('\\begin{description}\n')
 
@@ -1928,7 +1935,7 @@
         self.duclass_close(node)
 
     def visit_definition_list_item(self, node) -> None:
-        pass
+        self.out += self.ids_to_labels(node, newline=True)
 
     def depart_definition_list_item(self, node) -> None:
         if node.next_node(descend=False, siblings=True) is not None:
@@ -2239,6 +2246,7 @@
         label = r'%s\%s{%s}%s' % (prefix, enumtype, counter_name, suffix)
         self._enumeration_counters.append(label)
 
+        self.out += self.ids_to_labels(node, pre_nl=True)
         self.duclass_open(node)
         if enum_level <= 4:
             self.out.append('\\begin{enumerate}')
@@ -2263,8 +2271,8 @@
         self._enumeration_counters.pop()
 
     def visit_field(self, node) -> None:
+        self.out += self.ids_to_labels(node, pre_nl=True)
         # output is done in field_body, field_name
-        pass
 
     def depart_field(self, node) -> None:
         pass
@@ -2278,6 +2286,7 @@
             self.out.append(r'\\'+'\n')
 
     def visit_field_list(self, node) -> None:
+        self.out += self.ids_to_labels(node, pre_nl=True)
         self.duclass_open(node)
         if self.out is not self.docinfo:
             self.provide_fallback('fieldlist')
@@ -2447,6 +2456,7 @@
     def visit_image(self, node) -> None:
         # <image> can be inline element, body element, or nested in a <figure>
         # in all three cases the <image> may also be nested in a <reference>
+        # TODO: "classes" attribute currently ignored!
         self.requirements['graphicx'] = self.graphicx_package
         attrs = node.attributes
         # convert image URI to filesystem path, do not adjust relative path:
@@ -2560,6 +2570,7 @@
                             '\\begin{DUlineblock}{\\DUlineblockindent}\n')
             # In rST, nested line-blocks cannot be given class arguments
         else:
+            self.out += self.ids_to_labels(node, pre_nl=True)
             self.duclass_open(node)
             self.out.append('\\begin{DUlineblock}{0em}\n')
             self.insert_align_declaration(node)
@@ -2569,6 +2580,7 @@
         self.duclass_close(node)
 
     def visit_list_item(self, node) -> None:
+        self.out += self.ids_to_labels(node, pre_nl=True)
         self.out.append('\n\\item ')
 
     def depart_list_item(self, node) -> None:
@@ -2784,6 +2796,7 @@
     def visit_option_list(self, node) -> None:
         self.provide_fallback('providelength', '_providelength')
         self.provide_fallback('optionlist')
+        self.out += self.ids_to_labels(node, pre_nl=True)
         self.duclass_open(node)
         self.out.append('\\begin{DUoptionlist}\n')
 
@@ -2792,7 +2805,7 @@
         self.duclass_close(node)
 
     def visit_option_list_item(self, node) -> None:
-        pass
+        self.out += self.ids_to_labels(node, newline=True)
 
     def depart_option_list_item(self, node) -> None:
         pass
@@ -2927,10 +2940,13 @@
         self.provide_fallback('rubric')
         # class wrapper would interfere with ``\section*"`` type commands
         # (spacing/indent of first paragraph)
-        self.out.append('\n\\DUrubric{')
+        self.out += self.ids_to_labels(node, pre_nl=True)
+        self.duclass_open(node)
+        self.out.append('\\DUrubric{')
 
     def depart_rubric(self, node) -> None:
         self.out.append('}\n')
+        self.duclass_close(node)
 
     def visit_section(self, node) -> None:
         # Update counter-prefix for compound enumerators
@@ -2972,6 +2988,7 @@
         self.section_level -= 1
 
     def visit_sidebar(self, node) -> None:
+        self.out += self.ids_to_labels(node, pre_nl=True)
         self.duclass_open(node)
         self.requirements['color'] = PreambleCmds.color
         self.provide_fallback('sidebar')
@@ -2988,12 +3005,15 @@
 
     def visit_attribution(self, node) -> None:
         prefix, suffix = self.attribution_formats[self.settings.attribution]
-        self.out.append('\\nopagebreak\n\n\\raggedleft ')
-        self.out.append(prefix)
+        self.out.append('\\nopagebreak\n')
+        self.out += self.ids_to_labels(node, pre_nl=True)
+        self.duclass_open(node)
+        self.out.append(f'\\raggedleft {prefix}')
         self.context.append(suffix)
 
     def depart_attribution(self, node) -> None:
         self.out.append(self.context.pop() + '\n')
+        self.duclass_close(node)
 
     def visit_status(self, node) -> None:
         self.visit_docinfo_item(node)
@@ -3281,7 +3301,7 @@
 
         # labels and PDF bookmark (sidebar entry)
         self.out.append('\n')  # start new paragraph
-        if node['names']:  # don't add labels just for auto-ids
+        if len(node['names']) > 1:  # don't add labels just for the auto-id
             self.out += self.ids_to_labels(node, newline=True)
         if (isinstance(node.next_node(), nodes.title)
             and 'local' not in node['classes']
Modified: trunk/docutils/test/functional/expected/latex_cornercases.tex
===================================================================
--- trunk/docutils/test/functional/expected/latex_cornercases.tex	2025-08-20 12:04:54 UTC (rev 10218)
+++ trunk/docutils/test/functional/expected/latex_cornercases.tex	2025-08-21 15:18:05 UTC (rev 10219)
@@ -139,7 +139,6 @@
 These tests contain syntax elements and combinations which may cause
 trouble for the LaTeX writer.
 
-\phantomsection\label{contents}
 \pdfbookmark[1]{Contents}{contents}
 \tableofcontents
 
Modified: trunk/docutils/test/functional/expected/latex_leavevmode.tex
===================================================================
--- trunk/docutils/test/functional/expected/latex_leavevmode.tex	2025-08-20 12:04:54 UTC (rev 10218)
+++ trunk/docutils/test/functional/expected/latex_leavevmode.tex	2025-08-21 15:18:05 UTC (rev 10219)
@@ -360,6 +360,7 @@
 \item[{Comment and Target}] \leavevmode
 % This is ignored.
 
+\phantomsection\label{foo}
 \begin{itemize}
 \item Comments and other “Invisible” nodes (substitution definitions,
 targets, pending) must be skipped when determining whether a
Modified: trunk/docutils/test/functional/expected/latex_memoir.tex
===================================================================
--- trunk/docutils/test/functional/expected/latex_memoir.tex	2025-08-20 12:04:54 UTC (rev 10218)
+++ trunk/docutils/test/functional/expected/latex_memoir.tex	2025-08-21 15:18:05 UTC (rev 10219)
@@ -271,7 +271,6 @@
 
 \pagebreak[4] % start ToC on new page
 
-\phantomsection\label{table-of-contents}
 \renewcommand{\contentsname}{Table of Contents}
 \tableofcontents
 
Modified: trunk/docutils/test/functional/expected/standalone_rst_latex.tex
===================================================================
--- trunk/docutils/test/functional/expected/standalone_rst_latex.tex	2025-08-20 12:04:54 UTC (rev 10218)
+++ trunk/docutils/test/functional/expected/standalone_rst_latex.tex	2025-08-21 15:18:05 UTC (rev 10219)
@@ -268,7 +268,6 @@
 
 \pagebreak[4] % start ToC on new page
 
-\phantomsection\label{table-of-contents}
 \pdfbookmark[1]{Table of Contents}{table-of-contents}
 \renewcommand{\contentsname}{Table of Contents}
 \tableofcontents
Modified: trunk/docutils/test/functional/expected/standalone_rst_xetex.tex
===================================================================
--- trunk/docutils/test/functional/expected/standalone_rst_xetex.tex	2025-08-20 12:04:54 UTC (rev 10218)
+++ trunk/docutils/test/functional/expected/standalone_rst_xetex.tex	2025-08-21 15:18:05 UTC (rev 10219)
@@ -135,7 +135,6 @@
 
 \pagebreak[4] % start ToC on new page
 
-\phantomsection\label{table-of-contents}
 \pdfbookmark[1]{Table of Contents}{table-of-contents}
 
 \begin{DUclass}{contents}
@@ -787,7 +786,6 @@
   \label{directives}%
 }
 
-\phantomsection\label{contents}
 
 \begin{DUclass}{contents}
 \begin{DUclass}{local}
Modified: trunk/docutils/test/test_writers/test_latex2e.py
===================================================================
--- trunk/docutils/test/test_writers/test_latex2e.py	2025-08-20 12:04:54 UTC (rev 10218)
+++ trunk/docutils/test/test_writers/test_latex2e.py	2025-08-21 15:18:05 UTC (rev 10219)
@@ -316,9 +316,11 @@
 \\hline
 \\end{longtable*}
 """],
+])
+
 # Test handling of IDs and custom class values
 # --------------------------------------------
-# targets with ID
+samples['IDs and classes'] = ({}, [
 ["""\
 A paragraph with _`inline target`.
 
@@ -335,6 +337,177 @@
 \phantomsection\label{block-target}
 \DUrole{custom}{\DUrole{paragraph}{Next paragraph.}}
 """],
+# admonition
+["""
+.. class:: cls1
+.. _label1:
+.. hint::
+   :name: label2
+   :class: cls2
+
+   Don't forget to breathe.
+""",
+r"""
+\phantomsection\label{label2}\label{label1}
+\begin{DUclass}{cls2}
+\begin{DUclass}{cls1}
+\begin{DUclass}{hint}
+\begin{DUadmonition}
+\DUtitle{Hint}
+
+Don't forget to breathe.
+\end{DUadmonition}
+\end{DUclass}
+\end{DUclass}
+\end{DUclass}
+"""],
+# block quote
+["""
+.. class:: cls1
+.. _label1:
+
+   Exlicit is better than implicit.
+
+   .. class:: attribute-cls cute
+   .. _a-tribution:
+
+   -- Zen of Python
+""",
+r"""
+\phantomsection\label{label1}
+\begin{DUclass}{cls1}
+\begin{quote}
+Exlicit is better than implicit.
+\nopagebreak
+
+\phantomsection\label{a-tribution}
+\begin{DUclass}{attribute-cls}
+\begin{DUclass}{cute}
+\raggedleft —Zen of Python
+\end{DUclass}
+\end{DUclass}
+\end{quote}
+\end{DUclass}
+"""],
+# bullet list
+["""
+.. class:: cls1
+.. _bullet1:
+
+* list item
+
+  .. class:: bullet-class
+  .. _b-item:
+
+* second bullet list item
+""",
+r"""
+\phantomsection\label{bullet1}
+\begin{DUclass}{cls1}
+\begin{itemize}
+\item list item
+
+\phantomsection\label{b-item}
+\item second bullet list item
+\end{itemize}
+\end{DUclass}
+"""],
+# definition list
+["""
+.. class:: def-list-class
+.. _def-list:
+
+definition
+  list
+
+  .. class:: def-item-class
+  .. _def-item:
+
+term
+  definition
+""",
+"""
+\\phantomsection\\label{def-list}
+\\begin{DUclass}{def-list-class}
+\\begin{description}
+\\item[{definition}] \n\
+list
+
+\\phantomsection\\label{def-item}
+\\item[{term}] \n\
+definition
+\\end{description}
+\\end{DUclass}
+"""],
+# enumerated list
+["""
+.. class:: cls1
+.. _enumerated1:
+
+#. list item
+
+   .. class:: e-item-class
+   .. _e-item:
+
+#. enumerated list item
+""",
+r"""
+\phantomsection\label{enumerated1}
+\begin{DUclass}{cls1}
+\begin{enumerate}
+\item list item
+
+\phantomsection\label{e-item}
+\item enumerated list item
+\end{enumerate}
+\end{DUclass}
+"""],
+# field list
+["""\
+Not a docinfo.
+
+.. class:: fieldlist-class
+.. _f-list:
+
+:field: list
+
+  .. class:: field-class
+  .. _f-list-item:
+
+:name: body
+""",
+r"""
+Not a docinfo.
+
+\phantomsection\label{f-list}
+\begin{DUclass}{fieldlist-class}
+\begin{DUfieldlist}
+\item[{field:}]
+list
+
+\phantomsection\label{f-list-item}
+\item[{name:}]
+body
+\end{DUfieldlist}
+\end{DUclass}
+"""],
+# line block
+["""\
+.. class:: lineblock-class
+.. _line-block:
+
+| line block
+| second line
+""",
+r"""
+\phantomsection\label{line-block}
+\begin{DUclass}{lineblock-class}
+\begin{DUlineblock}{0em}
+\item[] line block
+\item[] second line
+\end{DUlineblock}
+\end{DUclass}
+"""],
 # literal block
 ["""\
 .. class:: cls1
@@ -354,6 +527,28 @@
 \end{quote}
 \end{DUclass}
 """],
+# option list
+["""\
+.. class:: o-list-class
+.. _o-list:
+
+--an  option list
+
+  .. class:: option-class
+  .. _o-item:
+
+--another  option
+""",
+r"""
+\phantomsection\label{o-list}
+\begin{DUclass}{o-list-class}
+\begin{DUoptionlist}
+\item[-{}-an]  option list
+\phantomsection\label{o-item}
+\item[-{}-another]  option
+\end{DUoptionlist}
+\end{DUclass}
+"""],
 # table with IDs and custom + special class values
 ["""\
 .. class:: cls1
@@ -377,6 +572,145 @@
 \end{DUclass}
 \end{DUclass}
 """],
+# directives
+["""\
+.. compound::
+   :class: compoundclass
+   :name: com-pound
+
+   Compound paragraph 1
+
+   Compound paragraph 2
+
+.. container:: containerclass
+   :name: con-tainer
+
+   Container paragraph 1
+
+   Container paragraph 2
+""",
+r"""
+\phantomsection\label{com-pound}
+\begin{DUclass}{compound}
+\begin{DUclass}{compoundclass}
+Compound paragraph 1
+
+Compound paragraph 2
+\end{DUclass}
+\end{DUclass}
+
+\phantomsection\label{con-tainer}
+\begin{DUclass}{containerclass}
+Container paragraph 1
+
+Container paragraph 2
+\end{DUclass}
+"""],
+# figures and images
+["""\
+.. figure:: parrot.png
+   :figclass: figureclass
+   :figname: fig-ure
+
+   .. class:: f-caption-class
+   .. _f-caption:
+
+   A figure with caption
+
+   .. class:: legend-class
+   .. _le-gend:
+
+   A figure legend
+
+.. image:: parrot.png
+   :class: imgclass TODO ignored!
+   :name: i-mage
+   :target: example.org/parrots
+""",
+r"""
+\phantomsection\label{fig-ure}
+\begin{DUclass}{figureclass}
+\begin{figure}
+\noindent\makebox[\linewidth][c]{\includegraphics{parrot.png}}
+\caption{\label{f-caption}\DUrole{f-caption-class}{A figure with caption}}
+\begin{DUlegend}
+\phantomsection\label{le-gend}
+\DUrole{legend-class}{A figure legend}
+\end{DUlegend}
+\end{figure}
+\end{DUclass}
+
+\phantomsection\label{i-mage}
+\href{example.org/parrots}{\includegraphics{parrot.png}}
+"""],
+["""\
+.. math:: x = 2^4
+   :class: mathclass
+   :name: math-block
+
+.. note:: a specific admonition
+   :class: noteclass
+   :name: my-note
+
+.. _my-raw:
+.. raw:: latex pseudoxml xml
+   :class: rawclass
+
+   \\LaTeX
+
+.. sidebar:: sidebar title
+   :class: sideclass
+   :name: side-bar
+
+   sidebar content
+
+.. topic:: topic heading
+   :class: topicclass
+   :name: to-pic
+
+   topic content
+""",
+r"""%
+\phantomsection
+\DUrole{mathclass}{%
+\begin{equation*}
+x = 2^4
+\label{math-block}
+\end{equation*}
+}
+\phantomsection\label{my-note}
+\begin{DUclass}{noteclass}
+\begin{DUclass}{note}
+\begin{DUadmonition}
+\DUtitle{Note}
+
+a specific admonition
+\end{DUadmonition}
+\end{DUclass}
+\end{DUclass}
+
+\phantomsection\label{my-raw}\DUrole{rawclass}{\LaTeX}
+
+\phantomsection\label{side-bar}
+\begin{DUclass}{sideclass}
+\DUsidebar{
+\DUtitle{sidebar title}
+
+sidebar content
+}
+\end{DUclass}
+
+\phantomsection\label{to-pic}
+\begin{DUclass}{topic}
+\begin{DUclass}{topicclass}
+\begin{quote}
+\DUtitle{topic heading}
+
+topic content
+\end{quote}
+\end{DUclass}
+\end{DUclass}
+"""],
 ])
 
 samples['latex_sectnum'] = ({'sectnum_xform': False}, [
Modified: trunk/docutils/test/test_writers/test_latex2e_parts.py
===================================================================
--- trunk/docutils/test/test_writers/test_latex2e_parts.py	2025-08-20 12:04:54 UTC (rev 10218)
+++ trunk/docutils/test/test_writers/test_latex2e_parts.py	2025-08-21 15:18:05 UTC (rev 10219)
@@ -214,7 +214,6 @@
 ------------------
 """,
  {'body': r"""
-\phantomsection\label{contents}
 \pdfbookmark[1]{Contents}{contents}
 \tableofcontents
 
@@ -235,7 +234,6 @@
 -------------
 """,
  {'body': r"""
-\phantomsection\label{contents}
 \pdfbookmark[1]{Contents}{contents}
 \tableofcontents
 
@@ -256,7 +254,6 @@
 -------------
 """,
  {'body': r"""
-\phantomsection\label{contents}
 \pdfbookmark[1]{Contents}{contents}
 \setcounter{tocdepth}{1}
 \tableofcontents
@@ -286,7 +283,6 @@
   \label{section-with-local-toc}%
 }
 
-\phantomsection\label{contents}
 \mtcsettitle{secttoc}{}
 \secttoc
 
@@ -596,7 +592,6 @@
 -------------
 """,
  {'body': r"""
-\phantomsection\label{contents}
 \pdfbookmark[1]{Contents}{contents}
 \setcounter{tocdepth}{0}
 \tableofcontents
@@ -703,7 +698,6 @@
 Paragraph 2.
 """,
  {'body': r"""
-\phantomsection\label{table-of-contents}
 \pdfbookmark[1]{Table of Contents}{table-of-contents}
 
 \begin{DUclass}{contents}
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
 | 
| 
     
      
      
      From: <mi...@us...> - 2025-08-20 12:04:57
      
     
   | 
Revision: 10218
          http://sourceforge.net/p/docutils/code/10218
Author:   milde
Date:     2025-08-20 12:04:54 +0000 (Wed, 20 Aug 2025)
Log Message:
-----------
LaTeX writer: dont add `\phantomsection` for subtitle labels.
Modified Paths:
--------------
    trunk/docutils/HISTORY.rst
    trunk/docutils/docutils/writers/latex2e/__init__.py
    trunk/docutils/test/test_writers/test_latex2e_parts.py
Modified: trunk/docutils/HISTORY.rst
===================================================================
--- trunk/docutils/HISTORY.rst	2025-08-20 12:04:42 UTC (rev 10217)
+++ trunk/docutils/HISTORY.rst	2025-08-20 12:04:54 UTC (rev 10218)
@@ -45,7 +45,7 @@
 
   - Prepend ``\phantomsection`` to labelled math-blocks.
   - Fix cross-reference anchor placement in figures, images,
-    literal-blocks, and tables.
+    literal-blocks, tables, and (sub)titles.
   - Simplify code for nested image.
 
 
Modified: trunk/docutils/docutils/writers/latex2e/__init__.py
===================================================================
--- trunk/docutils/docutils/writers/latex2e/__init__.py	2025-08-20 12:04:42 UTC (rev 10217)
+++ trunk/docutils/docutils/writers/latex2e/__init__.py	2025-08-20 12:04:54 UTC (rev 10218)
@@ -1381,7 +1381,8 @@
     # -----------------
 
     def stylesheet_call(self, path):
-        """Return code to reference or embed stylesheet file `path`"""
+        """Return code to reference or embed stylesheet file `path`."""
+
         path = Path(path)
         # is it a package (no extension or *.sty) or "normal" tex code:
         is_package = path.suffix in ('.sty', '')
@@ -2518,8 +2519,9 @@
 
         # Handle "ids" attribute:
         # do we need a \phantomsection?
-        set_anchor = not (isinstance(node.parent, (nodes.caption, nodes.title))
-                          or isinstance(node, nodes.caption))
+        anchor_nodes = (nodes.caption, nodes.subtitle, nodes.title)
+        set_anchor = not (isinstance(node.parent, anchor_nodes)
+                          or isinstance(node, anchor_nodes))
         add_newline = isinstance(node, nodes.paragraph)
         self.out += self.ids_to_labels(node, set_anchor, newline=add_newline)
         # Handle "classes" attribute:
Modified: trunk/docutils/test/test_writers/test_latex2e_parts.py
===================================================================
--- trunk/docutils/test/test_writers/test_latex2e_parts.py	2025-08-20 12:04:42 UTC (rev 10217)
+++ trunk/docutils/test/test_writers/test_latex2e_parts.py	2025-08-20 12:04:54 UTC (rev 10218)
@@ -464,6 +464,45 @@
 \date{}
 """
   }],
+# document title and subtitle with labels
+["""\
+.. _top:
+
+The Document Title
+==================
+
+.. _what-for:
+
+for test purposes
+-----------------
+
+Links to top_ and what-for_.
+""",
+ {'body': r"""
+Links to \hyperref[top]{top} and \hyperref[what-for]{what-for}.
+""",
+  'body_pre_docinfo': '\\maketitle\n',
+  'fallbacks': r"""
+% subtitle (in document title)
+\providecommand*{\DUdocumentsubtitle}[1]{{\large #1}}
+""",
+  'pdfsetup': DEFAULT_PARTS['pdfsetup'] + r"""\hypersetup{
+  pdftitle={The Document Title},
+}
+""",
+  'subtitle': 'for test purposes',
+  'title': 'The Document Title',
+  'titledata': r"""\title{The Document Title%
+  \label{the-document-title}%
+  \label{top}%
+  \\%
+  \DUdocumentsubtitle{for test purposes}%
+  \label{for-test-purposes}%
+  \label{what-for}}
+\author{}
+\date{}
+"""
+  }],
 # template
 ["""\
 """,
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
 | 
| 
     
      
      
      From: <mi...@us...> - 2025-08-20 12:04:45
      
     
   | 
Revision: 10217
          http://sourceforge.net/p/docutils/code/10217
Author:   milde
Date:     2025-08-20 12:04:42 +0000 (Wed, 20 Aug 2025)
Log Message:
-----------
LaTeX writer: Simplify code for nested image.
Simplify logic for the insertion of newlines around
the image inclusion macro.
Modified Paths:
--------------
    trunk/docutils/HISTORY.rst
    trunk/docutils/docutils/writers/latex2e/__init__.py
Modified: trunk/docutils/HISTORY.rst
===================================================================
--- trunk/docutils/HISTORY.rst	2025-08-20 12:04:32 UTC (rev 10216)
+++ trunk/docutils/HISTORY.rst	2025-08-20 12:04:42 UTC (rev 10217)
@@ -46,6 +46,7 @@
   - Prepend ``\phantomsection`` to labelled math-blocks.
   - Fix cross-reference anchor placement in figures, images,
     literal-blocks, and tables.
+  - Simplify code for nested image.
 
 
 Release 0.22 (2026-07-29)
Modified: trunk/docutils/docutils/writers/latex2e/__init__.py
===================================================================
--- trunk/docutils/docutils/writers/latex2e/__init__.py	2025-08-20 12:04:32 UTC (rev 10216)
+++ trunk/docutils/docutils/writers/latex2e/__init__.py	2025-08-20 12:04:42 UTC (rev 10217)
@@ -1777,7 +1777,7 @@
         self.out.append('}')
 
     def visit_caption(self, node) -> None:
-        self.out.append('\n\\caption{')
+        self.out.append('\\caption{')
         self.visit_inline(node)
 
     def depart_caption(self, node) -> None:
@@ -2312,9 +2312,9 @@
             # The LaTeX "figure" environment always uses the full linewidth,
             # so "outer alignment" is ignored. Just write a comment.
             # TODO: use the wrapfigure environment?
-            self.out.append('\\begin{figure} %% align = "%s"\n' % alignment)
+            self.out.append('\\begin{figure} %% align = "%s"' % alignment)
         else:
-            self.out.append('\\begin{figure}\n')
+            self.out.append('\\begin{figure}')
 
     def depart_figure(self, node) -> None:
         self.out.append('\\end{figure}\n')
@@ -2444,6 +2444,8 @@
         return f'{value}\\DU{unit}dimen'
 
     def visit_image(self, node) -> None:
+        # <image> can be inline element, body element, or nested in a <figure>
+        # in all three cases the <image> may also be nested in a <reference>
         self.requirements['graphicx'] = self.graphicx_package
         attrs = node.attributes
         # convert image URI to filesystem path, do not adjust relative path:
@@ -2490,12 +2492,12 @@
                 f"width={self.to_latex_length(attrs['width'], node)}")
         pre.append(''.join(self.ids_to_labels(node, newline=True)))
         if not (self.is_inline(node)
-                or isinstance(node.parent, (nodes.figure, nodes.compound))):
+                or isinstance(node.parent, nodes.compound)):
             pre.append('\n')
-        if not (self.is_inline(node)
-                or isinstance(node.parent, nodes.figure)):
+        if not self.is_inline(node):
             post.append('\n')
         pre.reverse()
+        # now insert image code
         self.out.extend(pre)
         if imagepath.suffix == '.svg' and 'svg' in self.settings.stylesheet:
             cmd = 'includesvg'
@@ -2880,7 +2882,7 @@
                          ord('%'): '\\%',
                          ord('\\'): '\\\\',
                          }
-        if not (self.is_inline(node) or isinstance(node.parent, nodes.figure)):
+        if not self.is_inline(node):
             self.out.append('\n')
         # external reference (URL)
         if 'refuri' in node:
@@ -2910,7 +2912,7 @@
 
     def depart_reference(self, node) -> None:
         self.out.append('}')
-        if not (self.is_inline(node) or isinstance(node.parent, nodes.figure)):
+        if not self.is_inline(node):
             self.out.append('\n')
 
     def visit_revision(self, node) -> None:
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
 | 
| 
     
      
      
      From: <mi...@us...> - 2025-08-20 12:04:35
      
     
   | 
Revision: 10216
          http://sourceforge.net/p/docutils/code/10216
Author:   milde
Date:     2025-08-20 12:04:32 +0000 (Wed, 20 Aug 2025)
Log Message:
-----------
LaTeX writer: fix anchor placement for figures, images, literal blocks, tables.
New attribute "pre_nl" for `ids_to_labels()`: prepend newline to label
definitions if there are labels.
Move anchor and label definition(s) for images, figures, literal blocks,
and tables before class wrappers and element.
Now, after activating a link to the element, it is fully visible
in the PDF viewer.
Modified Paths:
--------------
    trunk/docutils/HISTORY.rst
    trunk/docutils/docutils/writers/latex2e/__init__.py
    trunk/docutils/test/functional/expected/latex_cornercases.tex
    trunk/docutils/test/functional/expected/latex_memoir.tex
    trunk/docutils/test/functional/expected/standalone_rst_latex.tex
    trunk/docutils/test/functional/expected/standalone_rst_xetex.tex
    trunk/docutils/test/test_writers/test_latex2e.py
Modified: trunk/docutils/HISTORY.rst
===================================================================
--- trunk/docutils/HISTORY.rst	2025-08-20 12:04:19 UTC (rev 10215)
+++ trunk/docutils/HISTORY.rst	2025-08-20 12:04:32 UTC (rev 10216)
@@ -44,6 +44,8 @@
 * docutils/writers/latex2e/__init__.py
 
   - Prepend ``\phantomsection`` to labelled math-blocks.
+  - Fix cross-reference anchor placement in figures, images,
+    literal-blocks, and tables.
 
 
 Release 0.22 (2026-07-29)
Modified: trunk/docutils/docutils/writers/latex2e/__init__.py
===================================================================
--- trunk/docutils/docutils/writers/latex2e/__init__.py	2025-08-20 12:04:19 UTC (rev 10215)
+++ trunk/docutils/docutils/writers/latex2e/__init__.py	2025-08-20 12:04:32 UTC (rev 10216)
@@ -1560,19 +1560,25 @@
                                    id for id in node['ids']))
 
     def ids_to_labels(self, node, set_anchor=True, protect=False,
-                      newline=False) -> list[str]:
+                      newline=False, pre_nl=False) -> list[str]:
         """Return label definitions for all ids of `node`.
 
         If `set_anchor` is True, an anchor is set with \\phantomsection.
         If `protect` is True, the \\label cmd is made robust.
         If `newline` is True, a newline is added if there are labels.
+        If `pre_nl` is True, a newline is prepended if there are labels.
+
+        Provisional.
         """
         prefix = '\\protect' if protect else ''
-        labels = [prefix + '\\label{%s}' % id for id in node['ids']]
-        if set_anchor and labels:
-            labels.insert(0, '\\phantomsection')
-        if newline and labels:
-            labels.append('\n')
+        labels = [f'{prefix}\\label{{{id}}}' for id in node['ids']]
+        if labels:
+            if set_anchor:
+                labels.insert(0, '\\phantomsection')
+            if newline:
+                labels.append('\n')
+            if pre_nl:
+                labels.insert(0, '\n')
         return labels
 
     def set_align_from_classes(self, node) -> None:
@@ -2297,6 +2303,7 @@
 
     def visit_figure(self, node) -> None:
         self.requirements['float'] = PreambleCmds.float
+        self.out += self.ids_to_labels(node, pre_nl=True)
         self.duclass_open(node)
         # The 'align' attribute sets the "outer alignment",
         # for "inner alignment" use LaTeX default alignment (similar to HTML)
@@ -2308,7 +2315,6 @@
             self.out.append('\\begin{figure} %% align = "%s"\n' % alignment)
         else:
             self.out.append('\\begin{figure}\n')
-        self.out += self.ids_to_labels(node, newline=True)
 
     def depart_figure(self, node) -> None:
         self.out.append('\\end{figure}\n')
@@ -2482,6 +2488,7 @@
         if 'width' in attrs:
             include_graphics_options.append(
                 f"width={self.to_latex_length(attrs['width'], node)}")
+        pre.append(''.join(self.ids_to_labels(node, newline=True)))
         if not (self.is_inline(node)
                 or isinstance(node.parent, (nodes.figure, nodes.compound))):
             pre.append('\n')
@@ -2501,7 +2508,7 @@
         self.out.extend(post)
 
     def depart_image(self, node) -> None:
-        self.out += self.ids_to_labels(node, newline=True)
+        pass
 
     def visit_inline(self, node) -> None:
         # This function is also called by the visiting functions for
@@ -2623,8 +2630,8 @@
         _use_listings = (literal_env == 'lstlisting') and _use_env
 
         # Labels and classes:
+        self.out += self.ids_to_labels(node, pre_nl=True)
         self.duclass_open(node)
-        self.out += self.ids_to_labels(node, newline=True)
         # Highlight code?
         if (not _plaintext
             and 'code' in node['classes']
@@ -3058,7 +3065,6 @@
         self.depart_admonition(node)
 
     def visit_table(self, node) -> None:
-        self.duclass_open(node)
         self.requirements['table'] = PreambleCmds.table
         if not self.settings.legacy_column_widths:
             self.requirements['table1'] = PreambleCmds.table_columnwidth
@@ -3090,9 +3096,9 @@
         # if it has no caption/title.
         # See visit_thead() for tables with caption.
         if not self.active_table.caption:
-            self.out.extend(self.ids_to_labels(
-                node, set_anchor=len(self.table_stack) != 1,
-                newline=True))
+            set_anchor = (len(self.table_stack) != 1)
+            self.out += self.ids_to_labels(node, set_anchor, pre_nl=True)
+        self.duclass_open(node)
         # TODO: Don't use a longtable or add \noindent before
         #       the next paragraph, when in a "compound paragraph".
         #       Start a new line or a new paragraph?
Modified: trunk/docutils/test/functional/expected/latex_cornercases.tex
===================================================================
--- trunk/docutils/test/functional/expected/latex_cornercases.tex	2025-08-20 12:04:19 UTC (rev 10215)
+++ trunk/docutils/test/functional/expected/latex_cornercases.tex	2025-08-20 12:04:32 UTC (rev 10216)
@@ -894,8 +894,8 @@
 \hline
 \end{longtable}
 
+\phantomsection\label{figure-label}
 \begin{figure}
-\phantomsection\label{figure-label}
 \noindent\makebox[\linewidth][c]{\includegraphics{../../../docs/user/rst/images/biohazard.png}}
 \caption{Figure with %
 \label{hypertarget-in-figure-caption}hypertarget in figure caption.}
@@ -905,8 +905,8 @@
 \end{DUlegend}
 \end{figure}
 
+\phantomsection\label{image-label}
 \includegraphics{../../../docs/user/rst/images/biohazard.png}
-\phantomsection\label{image-label}
 
 See \hyperref[hypertarget-in-plain-text]{hypertarget in plain text},
 \hyperref[table-label]{table label}, \hyperref[hypertarget-in-table-title]{hypertarget in table title},
Modified: trunk/docutils/test/functional/expected/latex_memoir.tex
===================================================================
--- trunk/docutils/test/functional/expected/latex_memoir.tex	2025-08-20 12:04:19 UTC (rev 10215)
+++ trunk/docutils/test/functional/expected/latex_memoir.tex	2025-08-20 12:04:32 UTC (rev 10216)
@@ -824,8 +824,8 @@
 
 Image with multiple IDs:
 
+\phantomsection\label{image-target-3}\label{image-target-2}\label{image-target-1}
 \includegraphics{../../../docs/user/rst/images/biohazard.png}
-\phantomsection\label{image-target-3}\label{image-target-2}\label{image-target-1}
 
 A centered image:
 
Modified: trunk/docutils/test/functional/expected/standalone_rst_latex.tex
===================================================================
--- trunk/docutils/test/functional/expected/standalone_rst_latex.tex	2025-08-20 12:04:19 UTC (rev 10215)
+++ trunk/docutils/test/functional/expected/standalone_rst_latex.tex	2025-08-20 12:04:32 UTC (rev 10216)
@@ -818,8 +818,8 @@
 
 Image with multiple IDs:
 
+\phantomsection\label{image-target-3}\label{image-target-2}\label{image-target-1}
 \includegraphics{../../../docs/user/rst/images/biohazard.png}
-\phantomsection\label{image-target-3}\label{image-target-2}\label{image-target-1}
 
 A centered image:
 
Modified: trunk/docutils/test/functional/expected/standalone_rst_xetex.tex
===================================================================
--- trunk/docutils/test/functional/expected/standalone_rst_xetex.tex	2025-08-20 12:04:19 UTC (rev 10215)
+++ trunk/docutils/test/functional/expected/standalone_rst_xetex.tex	2025-08-20 12:04:32 UTC (rev 10216)
@@ -843,8 +843,8 @@
 
 Image with multiple IDs:
 
+\phantomsection\label{image-target-3}\label{image-target-2}\label{image-target-1}
 \includegraphics{../../../docs/user/rst/images/biohazard.png}
-\phantomsection\label{image-target-3}\label{image-target-2}\label{image-target-1}
 
 A centered image:
 
Modified: trunk/docutils/test/test_writers/test_latex2e.py
===================================================================
--- trunk/docutils/test/test_writers/test_latex2e.py	2025-08-20 12:04:19 UTC (rev 10215)
+++ trunk/docutils/test/test_writers/test_latex2e.py	2025-08-20 12:04:32 UTC (rev 10216)
@@ -133,19 +133,27 @@
 ["""
 .. image:: larch-mini.jpg
    :target: larch.jpg
+   :name: the-larch
+   :class: currently ignored
    :align: center
 """,
 r"""
+\phantomsection\label{the-larch}
 \noindent\makebox[\linewidth][c]{\href{larch.jpg}{\includegraphics{larch-mini.jpg}}}
 """],
 ["""\
+.. _fig:larch:
+
 .. figure:: larch-mini.jpg
    :target: larch.jpg
+   :name: the-larch
 
    The larch
 """,
 r"""
+\phantomsection\label{fig-larch}
 \begin{figure}
+\phantomsection\label{the-larch}
 \noindent\makebox[\linewidth][c]{\href{larch.jpg}{\includegraphics{larch-mini.jpg}}}
 \caption{The larch}
 \end{figure}
@@ -327,6 +335,25 @@
 \phantomsection\label{block-target}
 \DUrole{custom}{\DUrole{paragraph}{Next paragraph.}}
 """],
+# literal block
+["""\
+.. class:: cls1
+.. _block1:
+
+::
+
+   1^2_3
+""",
+r"""
+\phantomsection\label{block1}
+\begin{DUclass}{cls1}
+\begin{quote}
+\begin{alltt}
+1^2_3
+\end{alltt}
+\end{quote}
+\end{DUclass}
+"""],
 # table with IDs and custom + special class values
 ["""\
 .. class:: cls1
@@ -341,9 +368,9 @@
    = =
 """,
 r"""
+\phantomsection\label{label2}\label{label1}
 \begin{DUclass}{cls2}
 \begin{DUclass}{cls1}
-\phantomsection\label{label2}\label{label1}
 \begin{longtable*}{ll}
 Y & N \\
 \end{longtable*}
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
 | 
| 
     
      
      
      From: <mi...@us...> - 2025-08-20 12:04:22
      
     
   | 
Revision: 10215
          http://sourceforge.net/p/docutils/code/10215
Author:   milde
Date:     2025-08-20 12:04:19 +0000 (Wed, 20 Aug 2025)
Log Message:
-----------
LaTeX writer: set anchor for math-block with target/label.
The "hyperref" LaTeX package sets an anchor for hyperlinks to math blocks,
if they are numbered. The "math" directive generates unnumbered math blocks.
Prepend a `\phantomsection` to manually set an anchor so that hyperlinks
go to the right place.
Modified Paths:
--------------
    trunk/docutils/HISTORY.rst
    trunk/docutils/docutils/writers/latex2e/__init__.py
    trunk/docutils/test/functional/expected/latex_memoir.tex
    trunk/docutils/test/functional/expected/standalone_rst_latex.tex
    trunk/docutils/test/functional/expected/standalone_rst_xetex.tex
Modified: trunk/docutils/HISTORY.rst
===================================================================
--- trunk/docutils/HISTORY.rst	2025-08-20 09:39:58 UTC (rev 10214)
+++ trunk/docutils/HISTORY.rst	2025-08-20 12:04:19 UTC (rev 10215)
@@ -41,7 +41,11 @@
 
   - Better error reports for hyperlinks with embedded URI or alias.
 
+* docutils/writers/latex2e/__init__.py
 
+  - Prepend ``\phantomsection`` to labelled math-blocks.
+
+
 Release 0.22 (2026-07-29)
 =========================
 
@@ -189,6 +193,7 @@
 
 * docutils/transforms/references.py
 
+  - Make `AnonymousHyperlinks` transform idempotent.
   - New transform `CitationReferences`. Marks citation_references
     as resolved if BibTeX is used by the backend (LaTeX).
 
@@ -196,10 +201,6 @@
 
   - Removed `Compound` transform.
 
-* docutils/transforms/references.py
-
-  - Make `AnonymousHyperlinks` transform idempotent.
-
 * docutils/transforms/universal.py
 
   - `Messages` transform now also handles "loose" system messages
Modified: trunk/docutils/docutils/writers/latex2e/__init__.py
===================================================================
--- trunk/docutils/docutils/writers/latex2e/__init__.py	2025-08-20 09:39:58 UTC (rev 10214)
+++ trunk/docutils/docutils/writers/latex2e/__init__.py	2025-08-20 12:04:19 UTC (rev 10215)
@@ -2725,16 +2725,21 @@
 
     def visit_math_block(self, node) -> None:
         self.requirements['amsmath'] = r'\usepackage{amsmath}'
+        math_env = pick_math_environment(node.astext())
+        self.out.append('%\n')
+        if node['ids'] and math_env.endswith('*'):  # non-numbered equation
+            self.out.append('\\phantomsection\n')
         for cls in node['classes']:
             self.provide_fallback('inline')
-            self.out.append(r'\DUrole{%s}{' % cls)
-        math_env = pick_math_environment(node.astext())
-        self.out += [f'%\n\\begin{{{math_env}}}\n',
+            self.out.append(f'\\DUrole{{{cls}}}{{%\n')
+        self.out += [f'\\begin{{{math_env}}}\n',
                      node.astext().translate(unichar2tex.uni2tex_table),
                      '\n',
                      *self.ids_to_labels(node, set_anchor=False, newline=True),
                      f'\\end{{{math_env}}}']
-        self.out.append('}' * len(node['classes']))
+        if node['classes']:
+            self.out.append('\n')
+            self.out.append('}' * len(node['classes']))
         raise nodes.SkipNode  # content already processed
 
     def depart_math_block(self, node) -> None:
Modified: trunk/docutils/test/functional/expected/latex_memoir.tex
===================================================================
--- trunk/docutils/test/functional/expected/latex_memoir.tex	2025-08-20 09:39:58 UTC (rev 10214)
+++ trunk/docutils/test/functional/expected/latex_memoir.tex	2025-08-20 12:04:19 UTC (rev 10215)
@@ -1807,6 +1807,7 @@
 
 The determinant of the matrix
 %
+\phantomsection
 \begin{equation*}
 \mathbf{M} = \left(\begin{matrix}a&b\\c&d\end{matrix}\right)
 \label{eq-m}
@@ -1827,6 +1828,7 @@
 
 The Schrödinger equation
 %
+\phantomsection
 \begin{equation*}
 i\hbar \frac{\partial }{\partial t}\Psi  = \hat{H}\Psi ,
 \label{eq-schrodinger}
Modified: trunk/docutils/test/functional/expected/standalone_rst_latex.tex
===================================================================
--- trunk/docutils/test/functional/expected/standalone_rst_latex.tex	2025-08-20 09:39:58 UTC (rev 10214)
+++ trunk/docutils/test/functional/expected/standalone_rst_latex.tex	2025-08-20 12:04:19 UTC (rev 10215)
@@ -1822,6 +1822,7 @@
 
 The determinant of the matrix
 %
+\phantomsection
 \begin{equation*}
 \mathbf{M} = \left(\begin{matrix}a&b\\c&d\end{matrix}\right)
 \label{eq-m}
@@ -1842,6 +1843,7 @@
 
 The Schrödinger equation
 %
+\phantomsection
 \begin{equation*}
 i\hbar \frac{\partial }{\partial t}\Psi  = \hat{H}\Psi ,
 \label{eq-schrodinger}
Modified: trunk/docutils/test/functional/expected/standalone_rst_xetex.tex
===================================================================
--- trunk/docutils/test/functional/expected/standalone_rst_xetex.tex	2025-08-20 09:39:58 UTC (rev 10214)
+++ trunk/docutils/test/functional/expected/standalone_rst_xetex.tex	2025-08-20 12:04:19 UTC (rev 10215)
@@ -1851,6 +1851,7 @@
 
 The determinant of the matrix
 %
+\phantomsection
 \begin{equation*}
 \mathbf{M} = \left(\begin{matrix}a&b\\c&d\end{matrix}\right)
 \label{eq-m}
@@ -1871,6 +1872,7 @@
 
 The Schrödinger equation
 %
+\phantomsection
 \begin{equation*}
 i\hbar \frac{\partial }{\partial t}\Psi  = \hat{H}\Psi ,
 \label{eq-schrodinger}
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
 | 
| 
     
      
      
      From: <mi...@us...> - 2025-08-20 09:40:02
      
     
   | 
Revision: 10214
          http://sourceforge.net/p/docutils/code/10214
Author:   milde
Date:     2025-08-20 09:39:58 +0000 (Wed, 20 Aug 2025)
Log Message:
-----------
Documentation fixes.
Fix docstring: `directives.misc.Include.insert_into_input_lines()`
returns None. (The empty list is returned by `Include.run()`.)
Small edits in the Document Tree Guide.
Modified Paths:
--------------
    trunk/docutils/docs/ref/doctree.rst
    trunk/docutils/docutils/parsers/rst/directives/misc.py
Modified: trunk/docutils/docs/ref/doctree.rst
===================================================================
--- trunk/docutils/docs/ref/doctree.rst	2025-08-20 08:32:28 UTC (rev 10213)
+++ trunk/docutils/docs/ref/doctree.rst	2025-08-20 09:39:58 UTC (rev 10214)
@@ -4385,8 +4385,8 @@
 The ``auto`` attribute is used to indicate automatically-numbered
 `\<footnote>`_, `\<footnote_reference>`_ and `\<title>`_ elements
 (via the `%auto.att`_ parameter entity).
-In <footnote> and <footnote_reference> elements, it also carries information
-about the label type: "1": auto-numbered_, "*": auto-symbol_.
+In `\<footnote>`_ and `\<footnote_reference>`_ elements, it also carries
+information about the label type ("1": auto-numbered_, "*": auto-symbol_).
 
 
 ``backrefs``
@@ -4925,11 +4925,14 @@
 The ``title`` attribute is used in the `\<document>`_ element to store the
 document's *metadata title*.
 
-It is set by the `"title" directive`_ or the `DocTitle transform`_.
-This title is typically not part of the rendered document.
-It is, for example, used as `HTML <title> element`_ and shown in a
-browser's title bar, in a user's history or bookmarks, or in search results.
+The attribute is set by the `"title" directive`_ or the
+`DocTitle transform`_.  It is typically not part of the rendered document
+but, for example, used as `HTML <title> element`_ and shown in a
+browser's title bar, a user's history or bookmarks, or search results.
 
+Its value may may differ from the *displayed title* which is stored in a
+`\<title>`_ element.
+
 .. _HTML <title> element:
     https://html.spec.whatwg.org/multipage/semantics.html#the-title-element
 
Modified: trunk/docutils/docutils/parsers/rst/directives/misc.py
===================================================================
--- trunk/docutils/docutils/parsers/rst/directives/misc.py	2025-08-20 08:32:28 UTC (rev 10213)
+++ trunk/docutils/docutils/parsers/rst/directives/misc.py	2025-08-20 09:39:58 UTC (rev 10214)
@@ -236,8 +236,6 @@
     def insert_into_input_lines(self, text: str) -> None:
         """Insert file content into the rST input of the calling parser.
 
-        Returns an empty list to comply with the API of `Directive.run()`.
-
         Provisional.
         """
         source = self.options['source']
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
 | 
| 
     
      
      
      From: <gr...@us...> - 2025-08-20 08:32:40
      
     
   | 
Revision: 10213
          http://sourceforge.net/p/docutils/code/10213
Author:   grubert
Date:     2025-08-20 08:32:28 +0000 (Wed, 20 Aug 2025)
Log Message:
-----------
version string 0.23b.dev
Modified Paths:
--------------
    trunk/sandbox/manpage-writer/expected/characters.man
    trunk/sandbox/manpage-writer/expected/compact_lists.man
    trunk/sandbox/manpage-writer/expected/docinfo-deu-l_de.man
    trunk/sandbox/manpage-writer/expected/docinfo-deu-l_en.man
    trunk/sandbox/manpage-writer/expected/docinfo-deu.man
    trunk/sandbox/manpage-writer/expected/docinfo-eng-l_de.man
    trunk/sandbox/manpage-writer/expected/docinfo-eng-l_en.man
    trunk/sandbox/manpage-writer/expected/docinfo-eng.man
    trunk/sandbox/manpage-writer/expected/dotted.man
    trunk/sandbox/manpage-writer/expected/indent.man
    trunk/sandbox/manpage-writer/expected/man-de.1.man
    trunk/sandbox/manpage-writer/expected/optionslisttest.man
    trunk/sandbox/manpage-writer/expected/optionstoo.man
    trunk/sandbox/manpage-writer/expected/optionstoo.ps
    trunk/sandbox/manpage-writer/expected/quotes.man
    trunk/sandbox/manpage-writer/expected/quotes.ps
    trunk/sandbox/manpage-writer/expected/ref-2025-urue.man
    trunk/sandbox/manpage-writer/expected/ref-2025.man
    trunk/sandbox/manpage-writer/expected/references.man
    trunk/sandbox/manpage-writer/expected/references.ps
    trunk/sandbox/manpage-writer/expected/refs-urue.man
    trunk/sandbox/manpage-writer/expected/refs.man
    trunk/sandbox/manpage-writer/expected/test.man
    trunk/sandbox/manpage-writer/expected/test.ps
    trunk/sandbox/manpage-writer/expected-mandoc/characters.man
    trunk/sandbox/manpage-writer/expected-mandoc/compact_lists.man
    trunk/sandbox/manpage-writer/expected-mandoc/docinfo-deu-l_de.man
    trunk/sandbox/manpage-writer/expected-mandoc/docinfo-deu-l_en.man
    trunk/sandbox/manpage-writer/expected-mandoc/docinfo-deu.man
    trunk/sandbox/manpage-writer/expected-mandoc/docinfo-eng-l_de.man
    trunk/sandbox/manpage-writer/expected-mandoc/docinfo-eng-l_en.man
    trunk/sandbox/manpage-writer/expected-mandoc/docinfo-eng.man
    trunk/sandbox/manpage-writer/expected-mandoc/dotted.man
    trunk/sandbox/manpage-writer/expected-mandoc/indent.man
    trunk/sandbox/manpage-writer/expected-mandoc/man-de.1.man
    trunk/sandbox/manpage-writer/expected-mandoc/optionslisttest.man
    trunk/sandbox/manpage-writer/expected-mandoc/optionstoo.man
    trunk/sandbox/manpage-writer/expected-mandoc/quotes.man
    trunk/sandbox/manpage-writer/expected-mandoc/references.man
    trunk/sandbox/manpage-writer/expected-mandoc/refs-urue.man
    trunk/sandbox/manpage-writer/expected-mandoc/test.man
Modified: trunk/sandbox/manpage-writer/expected/characters.man
===================================================================
--- trunk/sandbox/manpage-writer/expected/characters.man	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected/characters.man	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,5 +1,5 @@
 .\" Man page generated from reStructuredText
-.\" by the Docutils 0.22rc6.dev manpage writer.
+.\" by the Docutils 0.23b.dev manpage writer.
 .
 .
 .nr rst2man-indent-level 0
Modified: trunk/sandbox/manpage-writer/expected/compact_lists.man
===================================================================
--- trunk/sandbox/manpage-writer/expected/compact_lists.man	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected/compact_lists.man	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,5 +1,5 @@
 .\" Man page generated from reStructuredText
-.\" by the Docutils 0.22rc6.dev manpage writer.
+.\" by the Docutils 0.23b.dev manpage writer.
 .
 .
 .nr rst2man-indent-level 0
Modified: trunk/sandbox/manpage-writer/expected/docinfo-deu-l_de.man
===================================================================
--- trunk/sandbox/manpage-writer/expected/docinfo-deu-l_de.man	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected/docinfo-deu-l_de.man	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,5 +1,5 @@
 .\" Man page generated from reStructuredText
-.\" by the Docutils 0.22rc6.dev manpage writer.
+.\" by the Docutils 0.23b.dev manpage writer.
 .
 .
 .nr rst2man-indent-level 0
Modified: trunk/sandbox/manpage-writer/expected/docinfo-deu-l_en.man
===================================================================
--- trunk/sandbox/manpage-writer/expected/docinfo-deu-l_en.man	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected/docinfo-deu-l_en.man	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,5 +1,5 @@
 .\" Man page generated from reStructuredText
-.\" by the Docutils 0.22rc6.dev manpage writer.
+.\" by the Docutils 0.23b.dev manpage writer.
 .
 .
 .nr rst2man-indent-level 0
Modified: trunk/sandbox/manpage-writer/expected/docinfo-deu.man
===================================================================
--- trunk/sandbox/manpage-writer/expected/docinfo-deu.man	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected/docinfo-deu.man	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,5 +1,5 @@
 .\" Man page generated from reStructuredText
-.\" by the Docutils 0.22rc6.dev manpage writer.
+.\" by the Docutils 0.23b.dev manpage writer.
 .
 .
 .nr rst2man-indent-level 0
Modified: trunk/sandbox/manpage-writer/expected/docinfo-eng-l_de.man
===================================================================
--- trunk/sandbox/manpage-writer/expected/docinfo-eng-l_de.man	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected/docinfo-eng-l_de.man	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,5 +1,5 @@
 .\" Man page generated from reStructuredText
-.\" by the Docutils 0.22rc6.dev manpage writer.
+.\" by the Docutils 0.23b.dev manpage writer.
 .
 .
 .nr rst2man-indent-level 0
Modified: trunk/sandbox/manpage-writer/expected/docinfo-eng-l_en.man
===================================================================
--- trunk/sandbox/manpage-writer/expected/docinfo-eng-l_en.man	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected/docinfo-eng-l_en.man	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,5 +1,5 @@
 .\" Man page generated from reStructuredText
-.\" by the Docutils 0.22rc6.dev manpage writer.
+.\" by the Docutils 0.23b.dev manpage writer.
 .
 .
 .nr rst2man-indent-level 0
Modified: trunk/sandbox/manpage-writer/expected/docinfo-eng.man
===================================================================
--- trunk/sandbox/manpage-writer/expected/docinfo-eng.man	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected/docinfo-eng.man	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,5 +1,5 @@
 .\" Man page generated from reStructuredText
-.\" by the Docutils 0.22rc6.dev manpage writer.
+.\" by the Docutils 0.23b.dev manpage writer.
 .
 .
 .nr rst2man-indent-level 0
Modified: trunk/sandbox/manpage-writer/expected/dotted.man
===================================================================
--- trunk/sandbox/manpage-writer/expected/dotted.man	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected/dotted.man	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,5 +1,5 @@
 .\" Man page generated from reStructuredText
-.\" by the Docutils 0.22rc6.dev manpage writer.
+.\" by the Docutils 0.23b.dev manpage writer.
 .
 .
 .nr rst2man-indent-level 0
Modified: trunk/sandbox/manpage-writer/expected/indent.man
===================================================================
--- trunk/sandbox/manpage-writer/expected/indent.man	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected/indent.man	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,5 +1,5 @@
 .\" Man page generated from reStructuredText
-.\" by the Docutils 0.22rc6.dev manpage writer.
+.\" by the Docutils 0.23b.dev manpage writer.
 .
 .
 .nr rst2man-indent-level 0
Modified: trunk/sandbox/manpage-writer/expected/man-de.1.man
===================================================================
--- trunk/sandbox/manpage-writer/expected/man-de.1.man	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected/man-de.1.man	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,6 +1,6 @@
 '\" t
 .\" Man page generated from reStructuredText
-.\" by the Docutils 0.22rc6.dev manpage writer.
+.\" by the Docutils 0.23b.dev manpage writer.
 .
 .
 .nr rst2man-indent-level 0
Modified: trunk/sandbox/manpage-writer/expected/optionslisttest.man
===================================================================
--- trunk/sandbox/manpage-writer/expected/optionslisttest.man	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected/optionslisttest.man	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,5 +1,5 @@
 .\" Man page generated from reStructuredText
-.\" by the Docutils 0.22rc6.dev manpage writer.
+.\" by the Docutils 0.23b.dev manpage writer.
 .
 .
 .nr rst2man-indent-level 0
Modified: trunk/sandbox/manpage-writer/expected/optionstoo.man
===================================================================
--- trunk/sandbox/manpage-writer/expected/optionstoo.man	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected/optionstoo.man	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,5 +1,5 @@
 .\" Man page generated from reStructuredText
-.\" by the Docutils 0.22rc6.dev manpage writer.
+.\" by the Docutils 0.23b.dev manpage writer.
 .
 .
 .nr rst2man-indent-level 0
Modified: trunk/sandbox/manpage-writer/expected/optionstoo.ps
===================================================================
--- trunk/sandbox/manpage-writer/expected/optionstoo.ps	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected/optionstoo.ps	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,6 +1,6 @@
 %!PS-Adobe-3.0
 %%Creator: groff version 1.23.0
-%%CreationDate: Thu Sep 28 08:45:02 2023
+%%CreationDate: Tue Jul  8 09:52:34 2025
 %%DocumentNeededResources: font Times-Italic
 %%+ font Times-Roman
 %%+ font Times-Bold
Modified: trunk/sandbox/manpage-writer/expected/quotes.man
===================================================================
--- trunk/sandbox/manpage-writer/expected/quotes.man	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected/quotes.man	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,5 +1,5 @@
 .\" Man page generated from reStructuredText
-.\" by the Docutils 0.22rc6.dev manpage writer.
+.\" by the Docutils 0.23b.dev manpage writer.
 .
 .
 .nr rst2man-indent-level 0
Modified: trunk/sandbox/manpage-writer/expected/quotes.ps
===================================================================
--- trunk/sandbox/manpage-writer/expected/quotes.ps	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected/quotes.ps	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,6 +1,6 @@
 %!PS-Adobe-3.0
 %%Creator: groff version 1.23.0
-%%CreationDate: Mon Aug 23 15:17:01 2021
+%%CreationDate: Tue Jul  8 09:52:34 2025
 %%DocumentNeededResources: font Times-Roman
 %%+ font Times-Bold
 %%DocumentSuppliedResources: procset grops 1.23 0
Modified: trunk/sandbox/manpage-writer/expected/ref-2025-urue.man
===================================================================
--- trunk/sandbox/manpage-writer/expected/ref-2025-urue.man	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected/ref-2025-urue.man	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,5 +1,5 @@
 .\" Man page generated from reStructuredText
-.\" by the Docutils 0.22rc6.dev manpage writer.
+.\" by the Docutils 0.23b.dev manpage writer.
 .
 .
 .nr rst2man-indent-level 0
Modified: trunk/sandbox/manpage-writer/expected/ref-2025.man
===================================================================
--- trunk/sandbox/manpage-writer/expected/ref-2025.man	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected/ref-2025.man	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,5 +1,5 @@
 .\" Man page generated from reStructuredText
-.\" by the Docutils 0.22rc6.dev manpage writer.
+.\" by the Docutils 0.23b.dev manpage writer.
 .
 .
 .nr rst2man-indent-level 0
Modified: trunk/sandbox/manpage-writer/expected/references.man
===================================================================
--- trunk/sandbox/manpage-writer/expected/references.man	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected/references.man	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,5 +1,5 @@
 .\" Man page generated from reStructuredText
-.\" by the Docutils 0.22rc6.dev manpage writer.
+.\" by the Docutils 0.23b.dev manpage writer.
 .
 .
 .nr rst2man-indent-level 0
Modified: trunk/sandbox/manpage-writer/expected/references.ps
===================================================================
--- trunk/sandbox/manpage-writer/expected/references.ps	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected/references.ps	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,6 +1,6 @@
 %!PS-Adobe-3.0
 %%Creator: groff version 1.23.0
-%%CreationDate: Tue Jun 10 19:58:59 2025
+%%CreationDate: Tue Jul  8 09:52:34 2025
 %%DocumentNeededResources: font Times-Italic
 %%+ font Times-Roman
 %%+ font Times-Bold
Modified: trunk/sandbox/manpage-writer/expected/refs-urue.man
===================================================================
--- trunk/sandbox/manpage-writer/expected/refs-urue.man	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected/refs-urue.man	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,5 +1,5 @@
 .\" Man page generated from reStructuredText
-.\" by the Docutils 0.22rc6.dev manpage writer.
+.\" by the Docutils 0.23b.dev manpage writer.
 .
 .
 .nr rst2man-indent-level 0
Modified: trunk/sandbox/manpage-writer/expected/refs.man
===================================================================
--- trunk/sandbox/manpage-writer/expected/refs.man	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected/refs.man	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,5 +1,5 @@
 .\" Man page generated from reStructuredText
-.\" by the Docutils 0.22rc6.dev manpage writer.
+.\" by the Docutils 0.23b.dev manpage writer.
 .
 .
 .nr rst2man-indent-level 0
Modified: trunk/sandbox/manpage-writer/expected/test.man
===================================================================
--- trunk/sandbox/manpage-writer/expected/test.man	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected/test.man	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,6 +1,6 @@
 '\" t
 .\" Man page generated from reStructuredText
-.\" by the Docutils 0.22rc6.dev manpage writer.
+.\" by the Docutils 0.23b.dev manpage writer.
 .
 .
 .nr rst2man-indent-level 0
Modified: trunk/sandbox/manpage-writer/expected/test.ps
===================================================================
--- trunk/sandbox/manpage-writer/expected/test.ps	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected/test.ps	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,6 +1,6 @@
 %!PS-Adobe-3.0
 %%Creator: groff version 1.23.0
-%%CreationDate: Sat Mar 16 15:50:40 2024
+%%CreationDate: Tue Jul  8 09:52:34 2025
 %%DocumentNeededResources: font Times-Italic
 %%+ font Times-Roman
 %%+ font Times-Bold
Modified: trunk/sandbox/manpage-writer/expected-mandoc/characters.man
===================================================================
--- trunk/sandbox/manpage-writer/expected-mandoc/characters.man	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected-mandoc/characters.man	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,5 +1,5 @@
 .\" Man page generated from reStructuredText
-.\" by the Docutils 0.22rc6.dev manpage writer.
+.\" by the Docutils 0.23b.dev manpage writer.
 .
 .
 .nr rst2man-indent-level 0
Modified: trunk/sandbox/manpage-writer/expected-mandoc/compact_lists.man
===================================================================
--- trunk/sandbox/manpage-writer/expected-mandoc/compact_lists.man	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected-mandoc/compact_lists.man	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,5 +1,5 @@
 .\" Man page generated from reStructuredText
-.\" by the Docutils 0.22rc6.dev manpage writer.
+.\" by the Docutils 0.23b.dev manpage writer.
 .
 .
 .nr rst2man-indent-level 0
Modified: trunk/sandbox/manpage-writer/expected-mandoc/docinfo-deu-l_de.man
===================================================================
--- trunk/sandbox/manpage-writer/expected-mandoc/docinfo-deu-l_de.man	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected-mandoc/docinfo-deu-l_de.man	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,5 +1,5 @@
 .\" Man page generated from reStructuredText
-.\" by the Docutils 0.22rc6.dev manpage writer.
+.\" by the Docutils 0.23b.dev manpage writer.
 .
 .
 .nr rst2man-indent-level 0
Modified: trunk/sandbox/manpage-writer/expected-mandoc/docinfo-deu-l_en.man
===================================================================
--- trunk/sandbox/manpage-writer/expected-mandoc/docinfo-deu-l_en.man	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected-mandoc/docinfo-deu-l_en.man	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,5 +1,5 @@
 .\" Man page generated from reStructuredText
-.\" by the Docutils 0.22rc6.dev manpage writer.
+.\" by the Docutils 0.23b.dev manpage writer.
 .
 .
 .nr rst2man-indent-level 0
Modified: trunk/sandbox/manpage-writer/expected-mandoc/docinfo-deu.man
===================================================================
--- trunk/sandbox/manpage-writer/expected-mandoc/docinfo-deu.man	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected-mandoc/docinfo-deu.man	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,5 +1,5 @@
 .\" Man page generated from reStructuredText
-.\" by the Docutils 0.22rc6.dev manpage writer.
+.\" by the Docutils 0.23b.dev manpage writer.
 .
 .
 .nr rst2man-indent-level 0
Modified: trunk/sandbox/manpage-writer/expected-mandoc/docinfo-eng-l_de.man
===================================================================
--- trunk/sandbox/manpage-writer/expected-mandoc/docinfo-eng-l_de.man	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected-mandoc/docinfo-eng-l_de.man	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,5 +1,5 @@
 .\" Man page generated from reStructuredText
-.\" by the Docutils 0.22rc6.dev manpage writer.
+.\" by the Docutils 0.23b.dev manpage writer.
 .
 .
 .nr rst2man-indent-level 0
Modified: trunk/sandbox/manpage-writer/expected-mandoc/docinfo-eng-l_en.man
===================================================================
--- trunk/sandbox/manpage-writer/expected-mandoc/docinfo-eng-l_en.man	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected-mandoc/docinfo-eng-l_en.man	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,5 +1,5 @@
 .\" Man page generated from reStructuredText
-.\" by the Docutils 0.22rc6.dev manpage writer.
+.\" by the Docutils 0.23b.dev manpage writer.
 .
 .
 .nr rst2man-indent-level 0
Modified: trunk/sandbox/manpage-writer/expected-mandoc/docinfo-eng.man
===================================================================
--- trunk/sandbox/manpage-writer/expected-mandoc/docinfo-eng.man	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected-mandoc/docinfo-eng.man	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,5 +1,5 @@
 .\" Man page generated from reStructuredText
-.\" by the Docutils 0.22rc6.dev manpage writer.
+.\" by the Docutils 0.23b.dev manpage writer.
 .
 .
 .nr rst2man-indent-level 0
Modified: trunk/sandbox/manpage-writer/expected-mandoc/dotted.man
===================================================================
--- trunk/sandbox/manpage-writer/expected-mandoc/dotted.man	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected-mandoc/dotted.man	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,5 +1,5 @@
 .\" Man page generated from reStructuredText
-.\" by the Docutils 0.22rc6.dev manpage writer.
+.\" by the Docutils 0.23b.dev manpage writer.
 .
 .
 .nr rst2man-indent-level 0
Modified: trunk/sandbox/manpage-writer/expected-mandoc/indent.man
===================================================================
--- trunk/sandbox/manpage-writer/expected-mandoc/indent.man	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected-mandoc/indent.man	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,5 +1,5 @@
 .\" Man page generated from reStructuredText
-.\" by the Docutils 0.22rc6.dev manpage writer.
+.\" by the Docutils 0.23b.dev manpage writer.
 .
 .
 .nr rst2man-indent-level 0
Modified: trunk/sandbox/manpage-writer/expected-mandoc/man-de.1.man
===================================================================
--- trunk/sandbox/manpage-writer/expected-mandoc/man-de.1.man	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected-mandoc/man-de.1.man	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,6 +1,6 @@
 '\" t
 .\" Man page generated from reStructuredText
-.\" by the Docutils 0.22rc6.dev manpage writer.
+.\" by the Docutils 0.23b.dev manpage writer.
 .
 .
 .nr rst2man-indent-level 0
Modified: trunk/sandbox/manpage-writer/expected-mandoc/optionslisttest.man
===================================================================
--- trunk/sandbox/manpage-writer/expected-mandoc/optionslisttest.man	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected-mandoc/optionslisttest.man	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,5 +1,5 @@
 .\" Man page generated from reStructuredText
-.\" by the Docutils 0.22rc6.dev manpage writer.
+.\" by the Docutils 0.23b.dev manpage writer.
 .
 .
 .nr rst2man-indent-level 0
Modified: trunk/sandbox/manpage-writer/expected-mandoc/optionstoo.man
===================================================================
--- trunk/sandbox/manpage-writer/expected-mandoc/optionstoo.man	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected-mandoc/optionstoo.man	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,5 +1,5 @@
 .\" Man page generated from reStructuredText
-.\" by the Docutils 0.22rc6.dev manpage writer.
+.\" by the Docutils 0.23b.dev manpage writer.
 .
 .
 .nr rst2man-indent-level 0
Modified: trunk/sandbox/manpage-writer/expected-mandoc/quotes.man
===================================================================
--- trunk/sandbox/manpage-writer/expected-mandoc/quotes.man	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected-mandoc/quotes.man	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,5 +1,5 @@
 .\" Man page generated from reStructuredText
-.\" by the Docutils 0.22rc6.dev manpage writer.
+.\" by the Docutils 0.23b.dev manpage writer.
 .
 .
 .nr rst2man-indent-level 0
Modified: trunk/sandbox/manpage-writer/expected-mandoc/references.man
===================================================================
--- trunk/sandbox/manpage-writer/expected-mandoc/references.man	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected-mandoc/references.man	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,5 +1,5 @@
 .\" Man page generated from reStructuredText
-.\" by the Docutils 0.22rc6.dev manpage writer.
+.\" by the Docutils 0.23b.dev manpage writer.
 .
 .
 .nr rst2man-indent-level 0
Modified: trunk/sandbox/manpage-writer/expected-mandoc/refs-urue.man
===================================================================
--- trunk/sandbox/manpage-writer/expected-mandoc/refs-urue.man	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected-mandoc/refs-urue.man	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,5 +1,5 @@
 .\" Man page generated from reStructuredText
-.\" by the Docutils 0.22rc6.dev manpage writer.
+.\" by the Docutils 0.23b.dev manpage writer.
 .
 .
 .nr rst2man-indent-level 0
Modified: trunk/sandbox/manpage-writer/expected-mandoc/test.man
===================================================================
--- trunk/sandbox/manpage-writer/expected-mandoc/test.man	2025-08-20 08:31:29 UTC (rev 10212)
+++ trunk/sandbox/manpage-writer/expected-mandoc/test.man	2025-08-20 08:32:28 UTC (rev 10213)
@@ -1,6 +1,6 @@
 '\" t
 .\" Man page generated from reStructuredText
-.\" by the Docutils 0.22rc6.dev manpage writer.
+.\" by the Docutils 0.23b.dev manpage writer.
 .
 .
 .nr rst2man-indent-level 0
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
 | 
| 
     
      
      
      From: <gr...@us...> - 2025-08-20 08:31:35
      
     
   | 
Revision: 10212
          http://sourceforge.net/p/docutils/code/10212
Author:   grubert
Date:     2025-08-20 08:31:29 +0000 (Wed, 20 Aug 2025)
Log Message:
-----------
set path to docutils dev code
Modified Paths:
--------------
    trunk/sandbox/manpage-writer/runtest
    trunk/sandbox/manpage-writer/runtest-mandoc
Modified: trunk/sandbox/manpage-writer/runtest
===================================================================
--- trunk/sandbox/manpage-writer/runtest	2025-08-19 20:28:48 UTC (rev 10211)
+++ trunk/sandbox/manpage-writer/runtest	2025-08-20 08:31:29 UTC (rev 10212)
@@ -7,6 +7,8 @@
 # Date: $Date$
 # Copyright: This script has been placed in the public domain.
 
+export PYTHONPATH=../../docutils
+
 IN_DIR=input
 OUT_DIR=output
 EXP_DIR=expected
Modified: trunk/sandbox/manpage-writer/runtest-mandoc
===================================================================
--- trunk/sandbox/manpage-writer/runtest-mandoc	2025-08-19 20:28:48 UTC (rev 10211)
+++ trunk/sandbox/manpage-writer/runtest-mandoc	2025-08-20 08:31:29 UTC (rev 10212)
@@ -24,6 +24,8 @@
 #           Supported values for the output argument are ascii, html, the default
 #           of locale, man, markdown, pdf, ps, tree, and utf8.
 
+export PYTHONPATH=../../docutils
+
 IN_DIR=input
 OUT_DIR=output-mandoc
 EXP_DIR=expected-mandoc
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
 | 
| 
     
      
      
      From: <mi...@us...> - 2025-08-19 20:28:50
      
     
   | 
Revision: 10211
          http://sourceforge.net/p/docutils/code/10211
Author:   milde
Date:     2025-08-19 20:28:48 +0000 (Tue, 19 Aug 2025)
Log Message:
-----------
Better error reports for hyperlinks with embedded URI or alias.
Check and report for common typos when an undefined referencname
contains a `<` or `>`.
Modified Paths:
--------------
    trunk/docutils/HISTORY.rst
    trunk/docutils/docutils/transforms/references.py
    trunk/docutils/test/test_transforms/test_hyperlinks.py
Modified: trunk/docutils/HISTORY.rst
===================================================================
--- trunk/docutils/HISTORY.rst	2025-08-19 18:38:11 UTC (rev 10210)
+++ trunk/docutils/HISTORY.rst	2025-08-19 20:28:48 UTC (rev 10211)
@@ -35,9 +35,13 @@
   - Better error messages for grid table markup errors (bug #504),
     based on patch #214 by Jynn Nelson.
 
-__ RELEASE-NOTES.html#nested-parsing
+  __ RELEASE-NOTES.html#nested-parsing
 
+* docutils/transforms/references.py
 
+  - Better error reports for hyperlinks with embedded URI or alias.
+
+
 Release 0.22 (2026-07-29)
 =========================
 
Modified: trunk/docutils/docutils/transforms/references.py
===================================================================
--- trunk/docutils/docutils/transforms/references.py	2025-08-19 18:38:11 UTC (rev 10210)
+++ trunk/docutils/docutils/transforms/references.py	2025-08-19 20:28:48 UTC (rev 10211)
@@ -955,11 +955,29 @@
         if refname in self.document.nameids:
             msg = self.document.reporter.error(
                 'Duplicate target name, cannot be used as a unique '
-                'reference: "%s".' % (node['refname']), base_node=node)
+                f'reference: "{refname}".', base_node=node)
         else:
+            if '<' in refname or '>' in refname:
+                hint = 'Did you want to embed a URI or alias?'
+                if '<' not in refname:
+                    hint += '\nOpening bracket missing.'
+                elif ' <' not in refname:
+                    hint += ('\nThe embedded reference must be preceded'
+                             ' by whitespace.')
+                if '>' not in refname:
+                    hint += '\nClosing bracket missing.'
+                elif not refname.endswith('>'):
+                    hint += ('\nThe embedded reference must be the last text'
+                             ' before the end string.')
+                if '< ' in refname or ' >' in refname:
+                    hint += ('\nWhitespace around the embedded reference'
+                             ' is not allowed.')
+                details = [nodes.paragraph('', hint)]
+            else:
+                details = []
             msg = self.document.reporter.error(
-                f'Unknown target name: "{node["refname"]}".',
-                base_node=node)
+                      f'Unknown target name: "{refname}".',
+                      *details, base_node=node)
         msgid = self.document.set_id(msg)
         prb = nodes.problematic(node.rawsource, node.rawsource, refid=msgid)
         try:
Modified: trunk/docutils/test/test_transforms/test_hyperlinks.py
===================================================================
--- trunk/docutils/test/test_transforms/test_hyperlinks.py	2025-08-19 18:38:11 UTC (rev 10210)
+++ trunk/docutils/test/test_transforms/test_hyperlinks.py	2025-08-19 20:28:48 UTC (rev 10211)
@@ -432,6 +432,91 @@
         .
 """],
 ["""\
+`link <address >`_
+""",
+"""\
+<document source="test data">
+    <paragraph>
+        <problematic ids="problematic-1" refid="system-message-1">
+            `link <address >`_
+    <system_message backrefs="problematic-1" ids="system-message-1" level="3" line="1" source="test data" type="ERROR">
+        <paragraph>
+            Unknown target name: "link <address >".
+        <paragraph>
+            Did you want to embed a URI or alias?
+            Whitespace around the embedded reference is not allowed.
+"""],
+["""\
+`link < address>`_
+""",
+"""\
+<document source="test data">
+    <paragraph>
+        <problematic ids="problematic-1" refid="system-message-1">
+            `link < address>`_
+    <system_message backrefs="problematic-1" ids="system-message-1" level="3" line="1" source="test data" type="ERROR">
+        <paragraph>
+            Unknown target name: "link < address>".
+        <paragraph>
+            Did you want to embed a URI or alias?
+            Whitespace around the embedded reference is not allowed.
+"""],
+["""\
+`link <address> e`_
+""",
+"""\
+<document source="test data">
+    <paragraph>
+        <problematic ids="problematic-1" refid="system-message-1">
+            `link <address> e`_
+    <system_message backrefs="problematic-1" ids="system-message-1" level="3" line="1" source="test data" type="ERROR">
+        <paragraph>
+            Unknown target name: "link <address> e".
+        <paragraph>
+            Did you want to embed a URI or alias?
+            The embedded reference must be the last text before the end string.
+"""],
+["""\
+`link<address>`_
+""",
+"""\
+<document source="test data">
+    <paragraph>
+        <problematic ids="problematic-1" refid="system-message-1">
+            `link<address>`_
+    <system_message backrefs="problematic-1" ids="system-message-1" level="3" line="1" source="test data" type="ERROR">
+        <paragraph>
+            Unknown target name: "link<address>".
+        <paragraph>
+            Did you want to embed a URI or alias?
+            The embedded reference must be preceded by whitespace.
+"""],
+["""\
+`link <address`_
+`link address>`_
+""",
+"""\
+<document source="test data">
+    <paragraph>
+        <problematic ids="problematic-1" refid="system-message-1">
+            `link <address`_
+        \n\
+        <problematic ids="problematic-2" refid="system-message-2">
+            `link address>`_
+    <system_message backrefs="problematic-1" ids="system-message-1" level="3" line="1" source="test data" type="ERROR">
+        <paragraph>
+            Unknown target name: "link <address".
+        <paragraph>
+            Did you want to embed a URI or alias?
+            Closing bracket missing.
+    <system_message backrefs="problematic-2" ids="system-message-2" level="3" line="1" source="test data" type="ERROR">
+        <paragraph>
+            Unknown target name: "link address>".
+        <paragraph>
+            Did you want to embed a URI or alias?
+            Opening bracket missing.
+"""],
+["""\
 Hyperlinks with angle-bracketed text need escaping.
 
 See `Element \\<a>`_, `Element <b\\>`_, and `Element <c>\\ `_.
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
 | 
| 
     
      
      
      From: <mi...@us...> - 2025-08-19 18:38:14
      
     
   | 
Revision: 10210
          http://sourceforge.net/p/docutils/code/10210
Author:   milde
Date:     2025-08-19 18:38:11 +0000 (Tue, 19 Aug 2025)
Log Message:
-----------
latex writer: Fix/simplify footnote handling.
In Docutils, reference names for footnotes, citations, and
hyperlinks use the same namespace.
Change the definitions of `\DUfootnotemark` and `\DUfootnotetext`
(use `\label` and `\hyperref` instead of `\hypertarget`
and `\hyperlink`) to get corresponding behaviour in LaTeX.
This allows to remove the additional creation of a `\label` for
footnotes with "autonumber-label".
Adapt tests.
Modified Paths:
--------------
    trunk/docutils/docutils/writers/latex2e/__init__.py
    trunk/docutils/docutils/writers/latex2e/docutils.sty
    trunk/docutils/test/functional/expected/latex_leavevmode.tex
    trunk/docutils/test/functional/expected/latex_literal_block.tex
    trunk/docutils/test/functional/expected/latex_literal_block_fancyvrb.tex
    trunk/docutils/test/functional/expected/latex_literal_block_listings.tex
    trunk/docutils/test/functional/expected/latex_literal_block_verbatim.tex
    trunk/docutils/test/functional/expected/latex_literal_block_verbatimtab.tex
    trunk/docutils/test/functional/expected/latex_memoir.tex
    trunk/docutils/test/functional/expected/length_units_latex.tex
    trunk/docutils/test/functional/expected/standalone_rst_latex.tex
    trunk/docutils/test/functional/expected/standalone_rst_xetex.tex
    trunk/docutils/test/test_writers/test_latex2e.py
    trunk/docutils/test/test_writers/test_latex2e_parts.py
Modified: trunk/docutils/docutils/writers/latex2e/__init__.py
===================================================================
--- trunk/docutils/docutils/writers/latex2e/__init__.py	2025-08-19 18:37:57 UTC (rev 10209)
+++ trunk/docutils/docutils/writers/latex2e/__init__.py	2025-08-19 18:38:11 UTC (rev 10210)
@@ -1359,7 +1359,7 @@
             else:
                 # require a minimal version:
                 self.fallbacks['_docutils.sty'] = (
-                    r'\usepackage{docutils}[2024-09-24]')
+                    r'\usepackage{docutils}[2025-08-06]')
 
         self.stylesheet = [self.stylesheet_call(path)
                            for path in stylesheet_list]
@@ -2335,9 +2335,6 @@
                 num = '[%s]' % num
             self.out.append('%%\n\\DUfootnotetext{%s}{%s}{%s}{' %
                             (node['ids'][0], backref, self.encode(num)))
-            if node['ids'] == [nodes.make_id(n) for n in node['names']]:
-                # autonumber-label: create anchor
-                self.out += self.ids_to_labels(node)
             # prevent spurious whitespace if footnote starts with paragraph:
             if len(node) > 1 and isinstance(node[1], nodes.paragraph):
                 self.out.append('%')
Modified: trunk/docutils/docutils/writers/latex2e/docutils.sty
===================================================================
--- trunk/docutils/docutils/writers/latex2e/docutils.sty	2025-08-19 18:37:57 UTC (rev 10209)
+++ trunk/docutils/docutils/writers/latex2e/docutils.sty	2025-08-19 18:38:11 UTC (rev 10210)
@@ -22,7 +22,7 @@
 
 \NeedsTeXFormat{LaTeX2e}
 \ProvidesPackage{docutils}
-  [2024-09-24 macros for Docutils LaTeX output]
+  [2025-08-06 macros for Docutils LaTeX output]
 
 % Helpers
 % -------
@@ -124,15 +124,15 @@
 
 % footnotes::
 
-% numerical or symbol footnotes with hyperlinks and backlinks
+% numbered or symbol footnotes with hyperlinks and backlinks
 \providecommand*{\DUfootnotemark}[3]{%
   \raisebox{1em}{\hypertarget{#1}{}}%
-  \hyperlink{#2}{\textsuperscript{#3}}%
+  \hyperref[#2]{\textsuperscript{#3}}%
 }
 \providecommand{\DUfootnotetext}[4]{%
   \begingroup%
   \renewcommand{\thefootnote}{%
-    \protect\raisebox{1em}{\protect\hypertarget{#1}{}}%
+    \protect\phantomsection\protect\label{#1}
     \protect\hyperlink{#2}{#3}}%
   \footnotetext{#4}%
   \endgroup%
Modified: trunk/docutils/test/functional/expected/latex_leavevmode.tex
===================================================================
--- trunk/docutils/test/functional/expected/latex_leavevmode.tex	2025-08-19 18:37:57 UTC (rev 10209)
+++ trunk/docutils/test/functional/expected/latex_leavevmode.tex	2025-08-19 18:38:11 UTC (rev 10210)
@@ -74,15 +74,15 @@
     {\enddescription\endquote}
 \fi
 
-% numerical or symbol footnotes with hyperlinks and backlinks
+% numbered or symbol footnotes with hyperlinks and backlinks
 \providecommand*{\DUfootnotemark}[3]{%
   \raisebox{1em}{\hypertarget{#1}{}}%
-  \hyperlink{#2}{\textsuperscript{#3}}%
+  \hyperref[#2]{\textsuperscript{#3}}%
 }
 \providecommand{\DUfootnotetext}[4]{%
   \begingroup%
   \renewcommand{\thefootnote}{%
-    \protect\raisebox{1em}{\protect\hypertarget{#1}{}}%
+    \protect\phantomsection\protect\label{#1}
     \protect\hyperlink{#2}{#3}}%
   \footnotetext{#4}%
   \endgroup%
@@ -385,7 +385,7 @@
 \end{DUclass}
 
 \item[{Footnote}] %
-\DUfootnotetext{f1}{f1}{1}{\phantomsection\label{f1}%
+\DUfootnotetext{f1}{f1}{1}{%
 This footnote will move to the bottom of the page.
 }
 
Modified: trunk/docutils/test/functional/expected/latex_literal_block.tex
===================================================================
--- trunk/docutils/test/functional/expected/latex_literal_block.tex	2025-08-19 18:37:57 UTC (rev 10209)
+++ trunk/docutils/test/functional/expected/latex_literal_block.tex	2025-08-19 18:38:11 UTC (rev 10210)
@@ -19,7 +19,7 @@
 %%% User specified packages and stylesheets
 
 %%% Fallback definitions for Docutils-specific commands
-\usepackage{docutils}[2024-09-24]
+\usepackage{docutils}[2025-08-06]
 
 % character width in monospaced font
 \newlength{\ttemwidth}
Modified: trunk/docutils/test/functional/expected/latex_literal_block_fancyvrb.tex
===================================================================
--- trunk/docutils/test/functional/expected/latex_literal_block_fancyvrb.tex	2025-08-19 18:37:57 UTC (rev 10209)
+++ trunk/docutils/test/functional/expected/latex_literal_block_fancyvrb.tex	2025-08-19 18:38:11 UTC (rev 10210)
@@ -19,7 +19,7 @@
 %%% User specified packages and stylesheets
 
 %%% Fallback definitions for Docutils-specific commands
-\usepackage{docutils}[2024-09-24]
+\usepackage{docutils}[2025-08-06]
 
 % character width in monospaced font
 \newlength{\ttemwidth}
Modified: trunk/docutils/test/functional/expected/latex_literal_block_listings.tex
===================================================================
--- trunk/docutils/test/functional/expected/latex_literal_block_listings.tex	2025-08-19 18:37:57 UTC (rev 10209)
+++ trunk/docutils/test/functional/expected/latex_literal_block_listings.tex	2025-08-19 18:38:11 UTC (rev 10210)
@@ -25,7 +25,7 @@
 %%% User specified packages and stylesheets
 
 %%% Fallback definitions for Docutils-specific commands
-\usepackage{docutils}[2024-09-24]
+\usepackage{docutils}[2025-08-06]
 
 % character width in monospaced font
 \newlength{\ttemwidth}
Modified: trunk/docutils/test/functional/expected/latex_literal_block_verbatim.tex
===================================================================
--- trunk/docutils/test/functional/expected/latex_literal_block_verbatim.tex	2025-08-19 18:37:57 UTC (rev 10209)
+++ trunk/docutils/test/functional/expected/latex_literal_block_verbatim.tex	2025-08-19 18:38:11 UTC (rev 10210)
@@ -18,7 +18,7 @@
 %%% User specified packages and stylesheets
 
 %%% Fallback definitions for Docutils-specific commands
-\usepackage{docutils}[2024-09-24]
+\usepackage{docutils}[2025-08-06]
 
 % character width in monospaced font
 \newlength{\ttemwidth}
Modified: trunk/docutils/test/functional/expected/latex_literal_block_verbatimtab.tex
===================================================================
--- trunk/docutils/test/functional/expected/latex_literal_block_verbatimtab.tex	2025-08-19 18:37:57 UTC (rev 10209)
+++ trunk/docutils/test/functional/expected/latex_literal_block_verbatimtab.tex	2025-08-19 18:38:11 UTC (rev 10210)
@@ -19,7 +19,7 @@
 %%% User specified packages and stylesheets
 
 %%% Fallback definitions for Docutils-specific commands
-\usepackage{docutils}[2024-09-24]
+\usepackage{docutils}[2025-08-06]
 
 % character width in monospaced font
 \newlength{\ttemwidth}
Modified: trunk/docutils/test/functional/expected/latex_memoir.tex
===================================================================
--- trunk/docutils/test/functional/expected/latex_memoir.tex	2025-08-19 18:37:57 UTC (rev 10209)
+++ trunk/docutils/test/functional/expected/latex_memoir.tex	2025-08-19 18:38:11 UTC (rev 10210)
@@ -88,15 +88,15 @@
     {\enddescription\endquote}
 \fi
 
-% numerical or symbol footnotes with hyperlinks and backlinks
+% numbered or symbol footnotes with hyperlinks and backlinks
 \providecommand*{\DUfootnotemark}[3]{%
   \raisebox{1em}{\hypertarget{#1}{}}%
-  \hyperlink{#2}{\textsuperscript{#3}}%
+  \hyperref[#2]{\textsuperscript{#3}}%
 }
 \providecommand{\DUfootnotetext}[4]{%
   \begingroup%
   \renewcommand{\thefootnote}{%
-    \protect\raisebox{1em}{\protect\hypertarget{#1}{}}%
+    \protect\phantomsection\protect\label{#1}
     \protect\hyperlink{#2}{#3}}%
   \footnotetext{#4}%
   \endgroup%
@@ -709,7 +709,7 @@
 This is the footnote's second paragraph.
 }
 %
-\DUfootnotetext{label}{footnote-reference-3}{2}{\phantomsection\label{label}%
+\DUfootnotetext{label}{footnote-reference-3}{2}{%
 Footnotes may be numbered, either manually (as in\DUfootnotemark{footnote-reference-5}{footnote-1}{1}) or
 automatically using a \textquotedbl{}\#\textquotedbl{}-prefixed label.  This footnote has a
 label so it can be referred to from multiple places, both as a
Modified: trunk/docutils/test/functional/expected/length_units_latex.tex
===================================================================
--- trunk/docutils/test/functional/expected/length_units_latex.tex	2025-08-19 18:37:57 UTC (rev 10209)
+++ trunk/docutils/test/functional/expected/length_units_latex.tex	2025-08-19 18:38:11 UTC (rev 10210)
@@ -22,7 +22,7 @@
 \usepackage{nohyperref}
 
 %%% Fallback definitions for Docutils-specific commands
-\usepackage{docutils}[2024-09-24]
+\usepackage{docutils}[2025-08-06]
 
 \ifdefined\DUchdimen  % lengh unit "ch": width of a zero char
 \else
Modified: trunk/docutils/test/functional/expected/standalone_rst_latex.tex
===================================================================
--- trunk/docutils/test/functional/expected/standalone_rst_latex.tex	2025-08-19 18:37:57 UTC (rev 10209)
+++ trunk/docutils/test/functional/expected/standalone_rst_latex.tex	2025-08-19 18:38:11 UTC (rev 10210)
@@ -92,15 +92,15 @@
     {\enddescription\endquote}
 \fi
 
-% numerical or symbol footnotes with hyperlinks and backlinks
+% numbered or symbol footnotes with hyperlinks and backlinks
 \providecommand*{\DUfootnotemark}[3]{%
   \raisebox{1em}{\hypertarget{#1}{}}%
-  \hyperlink{#2}{\textsuperscript{#3}}%
+  \hyperref[#2]{\textsuperscript{#3}}%
 }
 \providecommand{\DUfootnotetext}[4]{%
   \begingroup%
   \renewcommand{\thefootnote}{%
-    \protect\raisebox{1em}{\protect\hypertarget{#1}{}}%
+    \protect\phantomsection\protect\label{#1}
     \protect\hyperlink{#2}{#3}}%
   \footnotetext{#4}%
   \endgroup%
@@ -703,7 +703,7 @@
 This is the footnote’s second paragraph.
 }
 %
-\DUfootnotetext{label}{footnote-reference-3}{2}{\phantomsection\label{label}%
+\DUfootnotetext{label}{footnote-reference-3}{2}{%
 Footnotes may be numbered, either manually (as in\DUfootnotemark{footnote-reference-5}{footnote-1}{1}) or
 automatically using a “\#”-prefixed label.  This footnote has a
 label so it can be referred to from multiple places, both as a
Modified: trunk/docutils/test/functional/expected/standalone_rst_xetex.tex
===================================================================
--- trunk/docutils/test/functional/expected/standalone_rst_xetex.tex	2025-08-19 18:37:57 UTC (rev 10209)
+++ trunk/docutils/test/functional/expected/standalone_rst_xetex.tex	2025-08-19 18:38:11 UTC (rev 10210)
@@ -33,7 +33,7 @@
 %%% User specified packages and stylesheets
 
 %%% Fallback definitions for Docutils-specific commands
-\usepackage{docutils}[2024-09-24]
+\usepackage{docutils}[2025-08-06]
 \newcounter{enumv}
 
 \DUprovidelength{\pdfpxdimen}{1bp}
@@ -695,7 +695,7 @@
 This is the footnote’s second paragraph.
 }
 %
-\DUfootnotetext{label}{footnote-reference-3}{2}{\phantomsection\label{label}%
+\DUfootnotetext{label}{footnote-reference-3}{2}{%
 Footnotes may be numbered, either manually (as in\DUfootnotemark{footnote-reference-5}{footnote-1}{1}) or
 automatically using a “\#”-prefixed label.  This footnote has a
 label so it can be referred to from multiple places, both as a
Modified: trunk/docutils/test/test_writers/test_latex2e.py
===================================================================
--- trunk/docutils/test/test_writers/test_latex2e.py	2025-08-19 18:37:57 UTC (rev 10209)
+++ trunk/docutils/test/test_writers/test_latex2e.py	2025-08-19 18:38:11 UTC (rev 10210)
@@ -7,7 +7,7 @@
 """
 Tests for latex2e writer.
 
-This module test only the "body" part of the output.
+This module tests only the "body" part of the output.
 For tests of constructs that change the "head", see test-latex2e_parts.py.
 """
 
@@ -377,7 +377,7 @@
 Just a test citation [my_cite2006]_.
 
 .. [my_cite2006]
-   The underscore is mishandled.
+   Watch the underscore!
 """,
 r"""
 Just a test citation \cite{my_cite2006}.
@@ -384,7 +384,7 @@
 
 \begin{thebibliography}{my\_cite2006}
 \bibitem[my\_cite2006]{my_cite2006}{
-The underscore is mishandled.
+Watch the underscore!
 }
 \end{thebibliography}
 """],
@@ -499,6 +499,161 @@
 """],
 ])
 
+samples['docutils-footnotes'] = ({}, [
+# different markup variants
+[r"""
+Paragraphs contain text and may contain footnote references (manually
+numbered [1]_, anonymous auto-numbered [#]_, labeled auto-numbered
+[#label]_, or symbolic [*]_).
 
+.. [1] A footnote.
+
+.. [#label] Footnotes may be numbered, either manually or
+   automatically using a "#"-prefixed label.  This footnote has a
+   label so it can be referred to from multiple places, both as a
+   footnote reference ([#label]_) and as a `hyperlink reference`__.
+
+   __ label_
+
+.. [#] This footnote is numbered automatically and anonymously using a
+   label of "#" only.
+
+.. [*] Footnotes may also use symbols, specified with a "*" label.
+""",
+ r"""
+Paragraphs contain text and may contain footnote references (manually
+numbered\DUfootnotemark{footnote-reference-1}{footnote-1}{1}, anonymous auto-numbered\DUfootnotemark{footnote-reference-2}{footnote-2}{3}, labeled auto-numbered\DUfootnotemark{footnote-reference-3}{label}{2}, or symbolic\DUfootnotemark{footnote-reference-4}{footnote-3}{*}).
+%
+\DUfootnotetext{footnote-1}{footnote-reference-1}{1}{%
+A footnote.
+}
+%
+\DUfootnotetext{label}{footnote-reference-3}{2}{%
+Footnotes may be numbered, either manually or
+automatically using a \textquotedbl{}\#\textquotedbl{}-prefixed label.  This footnote has a
+label so it can be referred to from multiple places, both as a
+footnote reference (\DUfootnotemark{footnote-reference-5}{label}{2}) and as a \hyperref[label]{hyperlink reference}.
+}
+%
+\DUfootnotetext{footnote-2}{footnote-reference-2}{3}{%
+This footnote is numbered automatically and anonymously using a
+label of \textquotedbl{}\#\textquotedbl{} only.
+}
+%
+\DUfootnotetext{footnote-3}{footnote-reference-4}{*}{%
+Footnotes may also use symbols, specified with a \textquotedbl{}*\textquotedbl{} label.
+}
+"""],
+# nested footnotes
+["""\
+It's possible to produce nested footnotes in LaTeX. [#]_
+
+.. [#] It takes some work, though. [#]_
+.. [#] And don't even get me started on how tricky recursive footnotes
+       would be.
+""",
+r"""
+It's possible to produce nested footnotes in LaTeX.\DUfootnotemark{footnote-reference-1}{footnote-1}{1}
+%
+\DUfootnotetext{footnote-1}{footnote-reference-1}{1}{%
+It takes some work, though.\DUfootnotemark{footnote-reference-2}{footnote-2}{2}
+}
+%
+\DUfootnotetext{footnote-2}{footnote-reference-2}{2}{%
+And don't even get me started on how tricky recursive footnotes
+would be.
+}
+"""],
+# chained footnotes
+["""\
+It's possible to produce chained footnotes in LaTeX. [#]_
+
+.. [#] They're just a special case of nested footnotes. [#]_
+.. [#] A nested footnote is a footnote on a footnote. [#]_
+.. [#] This is a footnote on a footnote on a footnote.
+""",
+r"""
+It's possible to produce chained footnotes in LaTeX.\DUfootnotemark{footnote-reference-1}{footnote-1}{1}
+%
+\DUfootnotetext{footnote-1}{footnote-reference-1}{1}{%
+They're just a special case of nested footnotes.\DUfootnotemark{footnote-reference-2}{footnote-2}{2}
+}
+%
+\DUfootnotetext{footnote-2}{footnote-reference-2}{2}{%
+A nested footnote is a footnote on a footnote.\DUfootnotemark{footnote-reference-3}{footnote-3}{3}
+}
+%
+\DUfootnotetext{footnote-3}{footnote-reference-3}{3}{%
+This is a footnote on a footnote on a footnote.
+}
+"""],
+# multi-nested footnotes
+["""\
+A footnote [#multi]_
+
+.. [#multi] This is a footnote with nested [#]_ footnotes. [#]_ [#]_
+
+.. [#] First nested [#]_ footnote. [#]_
+
+.. [#] Second nested footnote. [#]_
+
+.. [#] Third nested footnote.
+
+.. [#] First double-nested footnote.
+
+.. [#] Second double-nested footnote.
+
+.. [#] First triple-nested footnote.
+
+.. [#] Not nested, referenced after footnote text.
+
+Ref to a new footnote [#]_
+
+A second reference to the first one [#multi]_.
+We can also write a hyperlink to multi_.
+""",
+r"""
+A footnote\DUfootnotemark{footnote-reference-1}{multi}{1}
+%
+\DUfootnotetext{multi}{footnote-reference-1}{1}{%
+This is a footnote with nested\DUfootnotemark{footnote-reference-2}{footnote-1}{2} footnotes.\DUfootnotemark{footnote-reference-3}{footnote-2}{3}\DUfootnotemark{footnote-reference-4}{footnote-3}{4}
+}
+%
+\DUfootnotetext{footnote-1}{footnote-reference-2}{2}{%
+First nested\DUfootnotemark{footnote-reference-5}{footnote-4}{5} footnote.\DUfootnotemark{footnote-reference-6}{footnote-5}{6}
+}
+%
+\DUfootnotetext{footnote-2}{footnote-reference-3}{3}{%
+Second nested footnote.\DUfootnotemark{footnote-reference-7}{footnote-6}{7}
+}
+%
+\DUfootnotetext{footnote-3}{footnote-reference-4}{4}{%
+Third nested footnote.
+}
+%
+\DUfootnotetext{footnote-4}{footnote-reference-5}{5}{%
+First double-nested footnote.
+}
+%
+\DUfootnotetext{footnote-5}{footnote-reference-6}{6}{%
+Second double-nested footnote.
+}
+%
+\DUfootnotetext{footnote-6}{footnote-reference-7}{7}{%
+First triple-nested footnote.
+}
+%
+\DUfootnotetext{footnote-7}{footnote-reference-8}{8}{%
+Not nested, referenced after footnote text.
+}
+
+Ref to a new footnote\DUfootnotemark{footnote-reference-8}{footnote-7}{8}
+
+A second reference to the first one\DUfootnotemark{footnote-reference-9}{multi}{1}.
+We can also write a hyperlink to \hyperref[multi]{multi}.
+"""],
+])
+
+
 if __name__ == '__main__':
     unittest.main()
Modified: trunk/docutils/test/test_writers/test_latex2e_parts.py
===================================================================
--- trunk/docutils/test/test_writers/test_latex2e_parts.py	2025-08-19 18:37:57 UTC (rev 10209)
+++ trunk/docutils/test/test_writers/test_latex2e_parts.py	2025-08-19 18:38:11 UTC (rev 10210)
@@ -191,15 +191,15 @@
 }
 """,
   'fallbacks': r"""
-% numerical or symbol footnotes with hyperlinks and backlinks
+% numbered or symbol footnotes with hyperlinks and backlinks
 \providecommand*{\DUfootnotemark}[3]{%
   \raisebox{1em}{\hypertarget{#1}{}}%
-  \hyperlink{#2}{\textsuperscript{#3}}%
+  \hyperref[#2]{\textsuperscript{#3}}%
 }
 \providecommand{\DUfootnotetext}[4]{%
   \begingroup%
   \renewcommand{\thefootnote}{%
-    \protect\raisebox{1em}{\protect\hypertarget{#1}{}}%
+    \protect\phantomsection\protect\label{#1}
     \protect\hyperlink{#2}{#3}}%
   \footnotetext{#4}%
   \endgroup%
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
 | 
| 
     
      
      
      From: <mi...@us...> - 2025-08-19 18:38:00
      
     
   | 
Revision: 10209
          http://sourceforge.net/p/docutils/code/10209
Author:   milde
Date:     2025-08-19 18:37:57 +0000 (Tue, 19 Aug 2025)
Log Message:
-----------
Use True/False instead of 1/0 for boolean `blank_finish`.
Modified Paths:
--------------
    trunk/docutils/docutils/parsers/rst/states.py
    trunk/docutils/docutils/statemachine.py
Modified: trunk/docutils/docutils/parsers/rst/states.py
===================================================================
--- trunk/docutils/docutils/parsers/rst/states.py	2025-08-19 18:37:49 UTC (rev 10208)
+++ trunk/docutils/docutils/parsers/rst/states.py	2025-08-19 18:37:57 UTC (rev 10209)
@@ -1648,7 +1648,7 @@
                   self.state_machine.input_lines[offset:],
                   input_offset=self.state_machine.abs_line_offset() + 1,
                   node=block, initial_state='LineBlock',
-                  blank_finish=0)
+                  blank_finish=False)
             self.goto_line(new_line_offset)
         if not blank_finish:
             self.parent += self.reporter.warning(
@@ -1743,7 +1743,7 @@
 
     def isolate_grid_table(self):
         messages = []
-        blank_finish = 1
+        blank_finish = True
         try:
             block = self.state_machine.get_text_block(flush_left=True)
         except statemachine.UnexpectedIndentationError as err:
@@ -1750,7 +1750,7 @@
             block, src, srcline = err.args
             messages.append(self.reporter.error('Unexpected indentation.',
                                                 source=src, line=srcline))
-            blank_finish = 0
+            blank_finish = False
         block.disconnect()
         # for East Asian chars:
         block.pad_double_width(self.double_width_pad_char)
@@ -1758,7 +1758,7 @@
         for i in range(len(block)):
             block[i] = block[i].strip()
             if block[i][0] not in '+|':  # check left edge
-                blank_finish = 0
+                blank_finish = False
                 self.state_machine.previous_line(len(block) - i)
                 del block[i:]
                 break
@@ -1768,7 +1768,7 @@
                 if self.grid_table_top_pat.match(block[i]):
                     self.state_machine.previous_line(len(block) - i + 1)
                     del block[i+1:]
-                    blank_finish = 0
+                    blank_finish = False
                     break
             else:
                 detail = 'Bottom border missing or corrupt.'
Modified: trunk/docutils/docutils/statemachine.py
===================================================================
--- trunk/docutils/docutils/statemachine.py	2025-08-19 18:37:49 UTC (rev 10208)
+++ trunk/docutils/docutils/statemachine.py	2025-08-19 18:37:57 UTC (rev 10209)
@@ -1406,7 +1406,7 @@
             stripped = line.lstrip()
             if not stripped:            # blank line
                 if until_blank:
-                    blank_finish = 1
+                    blank_finish = True
                     break
             elif block_indent is None:
                 line_indent = len(line) - len(stripped)
@@ -1416,7 +1416,7 @@
                     indent = min(indent, line_indent)
             end += 1
         else:
-            blank_finish = 1            # block ends at end of lines
+            blank_finish = True            # block ends at end of lines
         block = self[start:end]
         if first_indent is not None and block:
             block.data[0] = block.data[0][first_indent:]
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
 | 
| 
     
      
      
      From: <mi...@us...> - 2025-08-19 18:37:52
      
     
   | 
Revision: 10208
          http://sourceforge.net/p/docutils/code/10208
Author:   milde
Date:     2025-08-19 18:37:49 +0000 (Tue, 19 Aug 2025)
Log Message:
-----------
Better error reporting for table markup.
Based on [patches:#214] by Jynn Nelson.
Solves [bugs:#504].
Modified Paths:
--------------
    trunk/docutils/HISTORY.rst
    trunk/docutils/docutils/parsers/rst/states.py
    trunk/docutils/test/test_parsers/test_rst/test_tables.py
Modified: trunk/docutils/HISTORY.rst
===================================================================
--- trunk/docutils/HISTORY.rst	2025-08-19 17:03:49 UTC (rev 10207)
+++ trunk/docutils/HISTORY.rst	2025-08-19 18:37:49 UTC (rev 10208)
@@ -32,6 +32,8 @@
   - Ensure new "current node" is valid when switching section level
     (cf. bugs #508 and #509).
   - Use a `separate title style hierarchy for nested parsing`__.
+  - Better error messages for grid table markup errors (bug #504),
+    based on patch #214 by Jynn Nelson.
 
 __ RELEASE-NOTES.html#nested-parsing
 
Modified: trunk/docutils/docutils/parsers/rst/states.py
===================================================================
--- trunk/docutils/docutils/parsers/rst/states.py	2025-08-19 17:03:49 UTC (rev 10207)
+++ trunk/docutils/docutils/parsers/rst/states.py	2025-08-19 18:37:49 UTC (rev 10208)
@@ -1763,19 +1763,21 @@
                 del block[i:]
                 break
         if not self.grid_table_top_pat.match(block[-1]):  # find bottom
-            blank_finish = 0
             # from second-last to third line of table:
             for i in range(len(block) - 2, 1, -1):
                 if self.grid_table_top_pat.match(block[i]):
                     self.state_machine.previous_line(len(block) - i + 1)
                     del block[i+1:]
+                    blank_finish = 0
                     break
             else:
-                messages.extend(self.malformed_table(block))
+                detail = 'Bottom border missing or corrupt.'
+                messages.extend(self.malformed_table(block, detail, i))
                 return [], messages, blank_finish
         for i in range(len(block)):     # check right edge
             if len(block[i]) != width or block[i][-1] not in '+|':
-                messages.extend(self.malformed_table(block))
+                detail = 'Right border not aligned or missing.'
+                messages.extend(self.malformed_table(block, detail, i))
                 return [], messages, blank_finish
         return block, messages, blank_finish
 
@@ -1795,8 +1797,8 @@
                 if len(line.strip()) != toplen:
                     self.state_machine.next_line(i - start)
                     messages = self.malformed_table(
-                        lines[start:i+1], 'Bottom/header table border does '
-                        'not match top border.')
+                        lines[start:i+1], 'Bottom border or header rule does '
+                        'not match top border.', i-start)
                     return [], messages, i == limit or not lines[i+1].strip()
                 found += 1
                 found_at = i
@@ -1805,17 +1807,16 @@
                     break
             i += 1
         else:                           # reached end of input_lines
+            details = 'No bottom table border found'
             if found:
-                extra = ' or no blank line after table bottom'
+                details += ' or no blank line after table bottom'
                 self.state_machine.next_line(found_at - start)
                 block = lines[start:found_at+1]
             else:
-                extra = ''
                 self.state_machine.next_line(i - start - 1)
                 block = lines[start:]
-            messages = self.malformed_table(
-                block, 'No bottom table border found%s.' % extra)
-            return [], messages, not extra
+            messages = self.malformed_table(block, details + '.')
+            return [], messages, not found
         self.state_machine.next_line(end - start)
         block = lines[start:end+1]
         # for East Asian chars:
Modified: trunk/docutils/test/test_parsers/test_rst/test_tables.py
===================================================================
--- trunk/docutils/test/test_parsers/test_rst/test_tables.py	2025-08-19 17:03:49 UTC (rev 10207)
+++ trunk/docutils/test/test_parsers/test_rst/test_tables.py	2025-08-19 18:37:49 UTC (rev 10208)
@@ -86,20 +86,89 @@
 """],
 ["""\
 +-----------------------+
-| A malformed table. |
+| A misaligned table. |
 +-----------------------+
+
++-----------------------+
+| Right border missing.
++-----------------------+
 """,
 """\
 <document source="test data">
-    <system_message level="3" line="1" source="test data" type="ERROR">
+    <system_message level="3" line="2" source="test data" type="ERROR">
         <paragraph>
             Malformed table.
+            Right border not aligned or missing.
         <literal_block xml:space="preserve">
             +-----------------------+
-            | A malformed table. |
+            | A misaligned table. |
             +-----------------------+
+    <system_message level="3" line="6" source="test data" type="ERROR">
+        <paragraph>
+            Malformed table.
+            Right border not aligned or missing.
+        <literal_block xml:space="preserve">
+            +-----------------------+
+            | Right border missing.
+            +-----------------------+
 """],
 ["""\
++-------------------------+
+| A table with one cell   |
+| and missing bottom.     |
+""",
+"""\
+<document source="test data">
+    <system_message level="3" line="3" source="test data" type="ERROR">
+        <paragraph>
+            Malformed table.
+            Bottom border missing or corrupt.
+        <literal_block xml:space="preserve">
+            +-------------------------+
+            | A table with one cell   |
+            | and missing bottom.     |
+"""],
+["""\
++-------------------------+
+| A table with one cell   |
+| and corrupt bottom.     |
++------------------------ +
+""",
+"""\
+<document source="test data">
+    <system_message level="3" line="3" source="test data" type="ERROR">
+        <paragraph>
+            Malformed table.
+            Bottom border missing or corrupt.
+        <literal_block xml:space="preserve">
+            +-------------------------+
+            | A table with one cell   |
+            | and corrupt bottom.     |
+            +------------------------ +
+"""],
+["""\
++-------------------------+
+| A table with one cell   |
+| and corrupt bottom.     |
+--------------------------+
+""",
+"""\
+<document source="test data">
+    <system_message level="3" line="4" source="test data" type="ERROR">
+        <paragraph>
+            Malformed table.
+            Bottom border missing or corrupt.
+        <literal_block xml:space="preserve">
+            +-------------------------+
+            | A table with one cell   |
+            | and corrupt bottom.     |
+    <system_message level="2" line="4" source="test data" type="WARNING">
+        <paragraph>
+            Blank line required after table.
+    <paragraph>
+        --------------------------+
+"""],
+["""\
 +------------------------+
 | A well-formed | table. |
 +------------------------+
@@ -884,9 +953,9 @@
 ["""\
 ==============  ======
 A simple table  cell 2
-cell 3          cell 4
 ==============  ======
-No blank line after table.
+this could be   cell content
+or text after   the table
 """,
 """\
 <document source="test data">
@@ -897,13 +966,13 @@
         <literal_block xml:space="preserve">
             ==============  ======
             A simple table  cell 2
-            cell 3          cell 4
             ==============  ======
-    <system_message level="2" line="5" source="test data" type="WARNING">
+    <system_message level="2" line="4" source="test data" type="WARNING">
         <paragraph>
             Blank line required after table.
     <paragraph>
-        No blank line after table.
+        this could be   cell content
+        or text after   the table
 """],
 ["""\
 ==============  ======
@@ -1117,10 +1186,10 @@
 """,
 """\
 <document source="test data">
-    <system_message level="3" line="1" source="test data" type="ERROR">
+    <system_message level="3" line="4" source="test data" type="ERROR">
         <paragraph>
             Malformed table.
-            Bottom/header table border does not match top border.
+            Bottom border or header rule does not match top border.
         <literal_block xml:space="preserve">
             ==============  ======
             A simple table  this text extends to the right
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
 |