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

milde--- via Docutils-checkins <[email protected]> Sun, 02 Nov 2025 17:50:58 +0000
Newsgroups gmane.text.docutils.cvs
Message-ID <[email protected]>
Revision: 10254
          http://sourceforge.net/p/docutils/code/10254
Author:   milde
Date:     2025-11-02 17:50:57 +0000 (Sun, 02 Nov 2025)
Log Message:
-----------
Better error message for substitution definitions.

More informative and easier to comprehend error messages when a
substitution definition contains problematic or not supported content.

Document limits to content of substitution definitions.

Modified Paths:
--------------
    trunk/docutils/HISTORY.rst
    trunk/docutils/docs/ref/rst/directives.rst
    trunk/docutils/docutils/parsers/rst/states.py
    trunk/docutils/test/test_parsers/test_rst/test_directives/test_replace.py
    trunk/docutils/test/test_parsers/test_rst/test_substitutions.py
    trunk/docutils/test/test_transforms/test_hyperlinks.py
    trunk/docutils/test/test_transforms/test_substitutions.py

Modified: trunk/docutils/HISTORY.rst
===================================================================
--- trunk/docutils/HISTORY.rst	2025-10-06 12:29:47 UTC (rev 10253)
+++ trunk/docutils/HISTORY.rst	2025-11-02 17:50:57 UTC (rev 10254)
@@ -20,6 +20,8 @@
 * docutils/parsers/rst/states.py
 
   - Ignore combining characters when extracting a grid table block
+  - More informative error messages when a substitution definition contains
+    problematic or not supported content.
 
 * docutils/parsers/rst/tableparser.py
 

Modified: trunk/docutils/docs/ref/rst/directives.rst
===================================================================
--- trunk/docutils/docs/ref/rst/directives.rst	2025-10-06 12:29:47 UTC (rev 10253)
+++ trunk/docutils/docs/ref/rst/directives.rst	2025-11-02 17:50:57 UTC (rev 10254)
@@ -259,7 +259,7 @@
 .. _image options:
 
 The "image" directive recognizes the common options `class <class option_>`_
-and name_ as well as
+and name_ [#no-inline-name]_ as well as
 
 ``align`` : "top", "middle", "bottom", "left", "center", or "right"
     The alignment of the image, equivalent to the HTML ``<img>`` tag's
@@ -1424,9 +1424,12 @@
 
 The `image`_ directive can be used both, stand-alone (to define
 block-level images) and in substitution definitions to define
-inline images.
+inline images. [#no-inline-name]_
 
+.. [#no-inline-name] The name_ option cannot be used in
+                     a substitution definition. [#substitution-limits]_
 
+
 .. _replace:
 
 Replacement Text
@@ -1435,7 +1438,7 @@
 .. class:: field-indent-13em
 
 :Directive Type: "replace"
-:Doctree Element: Text & `inline elements`_
+:Doctree Element: Text & `inline elements`_ [#substitution-limits]_
 :Directive Arguments: none
 :Directive Options: none
 :Directive Content: A single paragraph; may contain inline markup.
@@ -1461,7 +1464,14 @@
     .. |Python| replace:: Python, *the* best language around
     .. _Python: https://www.python.org/
 
+.. [#substitution-limits] As a substitution may be referenced more than
+   one time, a substitution definition may not contain references to
+   `anonymous hyperlinks`_, `auto-numbered`_ or `auto-symbol`_ footnotes
+   and no `hyperlink targets`__ (names and identifiers must be unique).
 
+   __ restructuredtext.html#inline-internal-targets
+
+
 .. _unicode:
 
 Unicode Character Codes
@@ -2312,6 +2322,9 @@
     https://docs.python.org/3/library/codecs.html#standard-encodings
 
 .. _reStructuredText Markup Specification: restructuredtext.html
+.. _anonymous hyperlinks: restructuredtext.html#anonymous-hyperlinks
+.. _auto-numbered: restructuredtext.html#auto-numbered-footnotes
+.. _auto-symbol: restructuredtext.html#auto-symbol-footnotes
 .. _block quote: restructuredtext.html#block-quotes
 .. _citation references: restructuredtext.html#citation-references
 .. _citation: restructuredtext.html#citations

Modified: trunk/docutils/docutils/parsers/rst/states.py
===================================================================
--- trunk/docutils/docutils/parsers/rst/states.py	2025-10-06 12:29:47 UTC (rev 10253)
+++ trunk/docutils/docutils/parsers/rst/states.py	2025-11-02 17:50:57 UTC (rev 10254)
@@ -2189,15 +2189,23 @@
                 del substitution_node[i]
             else:
                 i += 1
-        for node in substitution_node.findall(nodes.Element):
-            if self.disallowed_inside_substitution_definitions(node):
-                pformat = nodes.literal_block('', node.pformat().rstrip())
+        for node in substitution_node.findall(nodes.Element,
+                                              include_self=False):
+            if isinstance(node, nodes.problematic):
                 msg = self.reporter.error(
-                    'Substitution definition contains illegal element <%s>:'
-                    % node.tagname,
-                    pformat, nodes.literal_block(blocktext, blocktext),
+                    'Problematic content in substitution definition',
+                    nodes.literal_block('', blocktext),
                     source=src, line=srcline)
+                msg.append(nodes.block_quote(
+                    '', nodes.paragraph('', '', *substitution_node.children)))
                 return [msg], blank_finish
+            illegal = self.disallowed_inside_substitution_definitions(node)
+            if illegal:
+                msg = self.reporter.error(f'{illegal} are not supported in '
+                                          'a substitution definition.',
+                                          nodes.literal_block('', blocktext),
+                                          source=src, line=srcline)
+                return [msg], blank_finish
         if len(substitution_node) == 0:
             msg = self.reporter.warning(
                   'Substitution definition "%s" empty or invalid.' % subname,
@@ -2208,13 +2216,15 @@
             substitution_node, subname, self.parent)
         return [substitution_node], blank_finish
 
-    def disallowed_inside_substitution_definitions(self, node) -> bool:
-        if (node['ids']
-            or isinstance(node, nodes.reference) and node.get('anonymous')
-            or isinstance(node, nodes.footnote_reference) and node.get('auto')):  # noqa: E501
-            return True
+    def disallowed_inside_substitution_definitions(self, node) -> str:
+        if isinstance(node, nodes.reference) and node.get('anonymous'):
+            return 'Anonymous references'
+        if isinstance(node, nodes.footnote_reference) and node.get('auto'):
+            return 'References to auto-numbered and auto-symbol footnotes'
+        if node['names'] or node['ids']:
+            return 'Targets (names and identifiers)'
         else:
-            return False
+            return ''
 
     def directive(self, match, **option_presets):
         """Returns a 2-tuple: list of nodes, and a "blank finish" boolean."""

Modified: trunk/docutils/test/test_parsers/test_rst/test_directives/test_replace.py
===================================================================
--- trunk/docutils/test/test_parsers/test_rst/test_directives/test_replace.py	2025-10-06 12:29:47 UTC (rev 10253)
+++ trunk/docutils/test/test_parsers/test_rst/test_directives/test_replace.py	2025-11-02 17:50:57 UTC (rev 10254)
@@ -23,6 +23,8 @@
 
 
 class ParserTestCase(unittest.TestCase):
+    maxDiff = None
+
     def test_parser(self):
         parser = Parser()
         settings = get_default_settings(Parser)
@@ -129,12 +131,20 @@
             Inline literal start-string without end-string.
     <system_message level="3" line="1" source="test data" type="ERROR">
         <paragraph>
-            Substitution definition contains illegal element <problematic>:
+            Problematic content in substitution definition
         <literal_block xml:space="preserve">
-            <problematic ids="problematic-1" refid="system-message-1">
-                *
-        <literal_block xml:space="preserve">
             .. |name| replace::  *error in **inline ``markup
+        <block_quote>
+            <paragraph>
+                <problematic ids="problematic-1" refid="system-message-1">
+                    *
+                error in \n\
+                <problematic ids="problematic-2" refid="system-message-2">
+                    **
+                inline \n\
+                <problematic ids="problematic-3" refid="system-message-3">
+                    ``
+                markup
 """],
 ["""\
 .. replace:: not valid outside of a substitution definition

Modified: trunk/docutils/test/test_parsers/test_rst/test_substitutions.py
===================================================================
--- trunk/docutils/test/test_parsers/test_rst/test_substitutions.py	2025-10-06 12:29:47 UTC (rev 10253)
+++ trunk/docutils/test/test_parsers/test_rst/test_substitutions.py	2025-11-02 17:50:57 UTC (rev 10254)
@@ -23,6 +23,8 @@
 
 
 class ParserTestCase(unittest.TestCase):
+    maxDiff = None
+
     def test_parser(self):
         parser = Parser()
         settings = get_default_settings(Parser)
@@ -287,26 +289,18 @@
         Elements that are prohibited inside of substitution definitions:
     <system_message level="3" line="3" source="test data" type="ERROR">
         <paragraph>
-            Substitution definition contains illegal element <target>:
+            Targets (names and identifiers) are not supported in a substitution definition.
         <literal_block xml:space="preserve">
-            <target ids="target" names="target">
-                target
-        <literal_block xml:space="preserve">
             .. |target| replace:: _`target`
     <system_message level="3" line="4" source="test data" type="ERROR">
         <paragraph>
-            Substitution definition contains illegal element <reference>:
+            Anonymous references are not supported in a substitution definition.
         <literal_block xml:space="preserve">
-            <reference anonymous="1" name="anonymous">
-                anonymous
-        <literal_block xml:space="preserve">
             .. |reference| replace:: anonymous__
     <system_message level="3" line="5" source="test data" type="ERROR">
         <paragraph>
-            Substitution definition contains illegal element <footnote_reference>:
+            References to auto-numbered and auto-symbol footnotes are not supported in a substitution definition.
         <literal_block xml:space="preserve">
-            <footnote_reference auto="1" ids="footnote-reference-1">
-        <literal_block xml:space="preserve">
             .. |auto-numbered footnote| replace:: [#]_
 """],
 ]

Modified: trunk/docutils/test/test_transforms/test_hyperlinks.py
===================================================================
--- trunk/docutils/test/test_transforms/test_hyperlinks.py	2025-10-06 12:29:47 UTC (rev 10253)
+++ trunk/docutils/test/test_transforms/test_hyperlinks.py	2025-11-02 17:50:57 UTC (rev 10254)
@@ -321,11 +321,8 @@
         match in this snippet.
     <system_message level="3" line="5" source="test data" type="ERROR">
         <paragraph>
-            Substitution definition contains illegal element <reference>:
+            Anonymous references are not supported in a substitution definition.
         <literal_block xml:space="preserve">
-            <reference anonymous="1" name="anonymous">
-                anonymous
-        <literal_block xml:space="preserve">
             .. |invalid| replace:: anonymous__
     <paragraph>
         <reference anonymous="1" name="hyperlink" refuri="URL">

Modified: trunk/docutils/test/test_transforms/test_substitutions.py
===================================================================
--- trunk/docutils/test/test_transforms/test_substitutions.py	2025-10-06 12:29:47 UTC (rev 10253)
+++ trunk/docutils/test/test_transforms/test_substitutions.py	2025-11-02 17:50:57 UTC (rev 10254)
@@ -25,6 +25,8 @@
 
 
 class TransformTestCase(unittest.TestCase):
+    maxDiff = None
+
     def test_transforms(self):
         parser = Parser()
         settings = get_default_settings(Parser)
@@ -378,11 +380,8 @@
         Substitution definition with an illegal element:
     <system_message level="3" line="3" source="test data" type="ERROR">
         <paragraph>
-            Substitution definition contains illegal element <target>:
+            Targets (names and identifiers) are not supported in a substitution definition.
         <literal_block xml:space="preserve">
-            <target ids="target" names="target">
-                target
-        <literal_block xml:space="preserve">
             .. |target| replace:: _`target`
     <paragraph>
         Make sure this substitution definition is not registered: \n\

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