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

milde--- via Docutils-checkins <[email protected]> Fri, 06 Feb 2026 09:09:19 +0000
Newsgroups gmane.text.docutils.cvs
Message-ID <[email protected]>
Revision: 10299
          http://sourceforge.net/p/docutils/code/10299
Author:   milde
Date:     2026-02-06 09:09:19 +0000 (Fri, 06 Feb 2026)
Log Message:
-----------
Fix nodes.transition.validate_position()

Ignore title elements as well as moving or invisible elements (except comments)
when validating the restriction:
"Transitions may not begin or end a section or document."

This brings the validation in line with the documentation in
"The Docutils Document Tree" updated in commit [r10268].

Modified Paths:
--------------
    trunk/docutils/HISTORY.rst
    trunk/docutils/docutils/nodes.py
    trunk/docutils/test/test_nodes.py

Modified: trunk/docutils/HISTORY.rst
===================================================================
--- trunk/docutils/HISTORY.rst	2026-02-06 09:09:11 UTC (rev 10298)
+++ trunk/docutils/HISTORY.rst	2026-02-06 09:09:19 UTC (rev 10299)
@@ -21,6 +21,13 @@
 
   - Apply patch #216 by Dmitry Shachnev: fix type annotations.
 
+* docutils/nodes.py
+
+  - `transition.validate_position()` now checks all siblings (not just
+    neighbours and also ignores <pending>, <substitution_definition>, and
+    <target> elements when testing for a <transition> at the begin or end
+    of a <section> or the <document>.
+
 * docutils/parsers/rst/directives/body.py
 
   - Add source and line info to <rubric> elements.

Modified: trunk/docutils/docutils/nodes.py
===================================================================
--- trunk/docutils/docutils/nodes.py	2026-02-06 09:09:11 UTC (rev 10298)
+++ trunk/docutils/docutils/nodes.py	2026-02-06 09:09:19 UTC (rev 10299)
@@ -1746,31 +1746,35 @@
 
 
 class transition(SubStructural, Element):
-    """Transitions__ are breaks between untitled text parts.
+    """Transitions__ represent "semantic breaks".
 
     __ https://docutils.sourceforge.io/docs/ref/doctree.html#transition
     """
+    # Sibling nodes that are ignored when validating a transition's position
+    # (titles plus moving and invisible elements except comments):
+    ignored_siblings = (decoration, meta, pending, substitution_definition,
+                        subtitle, target, title)
 
     def validate_position(self) -> None:
         """Check additional constraints on `transition` placement.
 
-        A transition may not begin or end a section or document,
+        A transition may not begin or end section or document text,
         nor may two transitions be immediately adjacent.
         """
         messages = [f'Element {self.parent.starttag()} invalid:']
-        predecessor = self.previous_sibling()
-        if (predecessor is None  # index == 0
-            or isinstance(predecessor, (title, subtitle, meta, decoration))
-            # A transition following these elements still counts as
-            # "at the beginning of a document or section".
-            ):
+        if isinstance(self.previous_sibling(), transition):
             messages.append(
+                '<transition> may not directly follow another transition.')
+        i = self.parent.index(self)
+        prev_siblings = self.parent[:i]
+        if not [sibling for sibling in prev_siblings
+                if not isinstance(sibling, self.ignored_siblings)]:
+            messages.append(
                 '<transition> may not begin a section or document.')
-        if self.parent.index(self) == len(self.parent) - 1:
+        next_siblings = self.parent[i+1:]
+        if not [sibling for sibling in next_siblings
+                if not isinstance(sibling, self.ignored_siblings)]:
             messages.append('<transition> may not end a section or document.')
-        if isinstance(predecessor, transition):
-            messages.append(
-                '<transition> may not directly follow another transition.')
         if len(messages) > 1:
             raise ValidationError('\n  '.join(messages),
                                   problematic_element=self)

Modified: trunk/docutils/test/test_nodes.py
===================================================================
--- trunk/docutils/test/test_nodes.py	2026-02-06 09:09:11 UTC (rev 10298)
+++ trunk/docutils/test/test_nodes.py	2026-02-06 09:09:19 UTC (rev 10299)
@@ -818,28 +818,38 @@
 
     def test_validate_content_transition(self):
         """Test additional constraints on <transition> placement:
-           Not at begin or end of a section or document,
+           Not at begin or end of section or document text,
            not after another transition.
         """
         transition = nodes.transition()
+        comment = nodes.comment()
         paragraph = nodes.paragraph()
-        section = nodes.section('', nodes.title(), transition, paragraph)
+        subdef = nodes.substitution_definition()
+        target = nodes.target()
+        title = nodes.title()
+
+        section = nodes.section('', title, comment, transition, paragraph)
+        self.assertEqual(section.validate_content(), [])
+        section = nodes.section('', title, subdef, transition, target, subdef)
         with self.assertRaisesRegex(nodes.ValidationError,
-                                    '<transition> may not begin a section '):
-            section.validate_content()
-        section = nodes.section('', nodes.title(), paragraph, transition)
+                                    '<transition> may not begin a section .*\n'
+                                    '  <transition> may not end a section '):
+            self.assertEqual(section.validate_content(), [])
+        section = nodes.section('', title, paragraph, transition,
+                                nodes.transition(), paragraph)
         with self.assertRaisesRegex(nodes.ValidationError,
-                                    '<transition> may not end a section '):
-            section.validate_content()
-        section = nodes.section('', nodes.title(), paragraph,
-                                nodes.transition(), transition)
-        with self.assertRaisesRegex(nodes.ValidationError,
                                     'Element <section> invalid:\n'
-                                    '  <transition> may not end .*\n'
-                                    '  <transition> may not directly '):
-            section.validate_content()
+                                    '  <transition> may not directly follow'):
+            self.assertEqual(section.validate_content(), [])
 
+        document = utils.new_document('test')
+        document.extend([nodes.decoration(), transition, nodes.meta()])
+        with self.assertRaisesRegex(nodes.ValidationError,
+                                    '<transition> may not begin a section .*\n'
+                                    '  <transition> may not end a section '):
+            self.assertEqual(section.validate_content(), [])
 
+
 class MiscTests(unittest.TestCase):
 
     def test_node_class_names(self):

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