|
From: <mi...@us...> - 2026-07-16 10:43:42
|
Revision: 10386
http://sourceforge.net/p/docutils/code/10386
Author: milde
Date: 2026-07-16 10:43:33 +0000 (Thu, 16 Jul 2026)
Log Message:
-----------
Fix issue with inline targets containing substring "refname" or "refuri".
`nodes.Node.next_node()` may return a `nodes.Text` instance
where ``if "refname" in node`` does not test for attributes but
the substring "refname" in the text content.
This led to `<target names="%refname.att">%refname.att</target>`
beeing wrongly identified as an indirect target and a subsequent
AssertionError because the corresponding `<reference>` did not get
a "refid" attribute.
Adding a check makes `PropagateTargets.next_suitable_target()`
return None if called with a non-empty start target.
For added robustness, `PropagateTargets.is_internal()` checks if
the given argument is a `nodes.Element` instance.
Modified Paths:
--------------
trunk/docutils/docutils/nodes.py
trunk/docutils/docutils/transforms/references.py
trunk/docutils/docutils/writers/_html_base.py
trunk/docutils/test/test_nodes.py
Modified: trunk/docutils/docutils/nodes.py
===================================================================
--- trunk/docutils/docutils/nodes.py 2026-07-16 10:43:24 UTC (rev 10385)
+++ trunk/docutils/docutils/nodes.py 2026-07-16 10:43:33 UTC (rev 10386)
@@ -688,6 +688,9 @@
def __contains__(self, key: str | Node) -> bool:
# Test for both, children and attributes with operator ``in``.
+ #
+ # Caution: Document Tree traversal also returns Text nodes where
+ # ``in`` looks for substrings in the text content.
if isinstance(key, str):
return key in self.attributes
return key in self.children
Modified: trunk/docutils/docutils/transforms/references.py
===================================================================
--- trunk/docutils/docutils/transforms/references.py 2026-07-16 10:43:24 UTC (rev 10385)
+++ trunk/docutils/docutils/transforms/references.py 2026-07-16 10:43:33 UTC (rev 10386)
@@ -133,8 +133,11 @@
target['names'] = []
def next_suitable_node(self, target: nodes.Element) -> nodes.Element:
- if not isinstance(target, nodes.target):
- return None # only <target> ids/names are propagated
+ # Return the next suitable element for the transfer of the
+ # "names" and "ids" attributes from an empty "internal" <target>
+ # (target propagation).
+ if not isinstance(target, nodes.target) or len(target):
+ return None
candidate = target.next_node(ascend=True)
# skip system messages (may be removed by universal.FilterMessages)
while isinstance(candidate, nodes.system_message):
@@ -147,8 +150,10 @@
return None
return candidate
- def is_internal(self, node: nodes.Element) -> bool:
- # Return True, if `node` is an internal hyperlink target.
+ def is_internal(self, node: nodes.Node) -> bool:
+ # Return True, if `node` is a potential internal hyperlink target.
+ if not isinstance(node, nodes.Element):
+ return False
if 'refid' in node or 'refname' in node or 'refuri' in node:
return False # node is indirect or external target
# check destination of chained targets:
Modified: trunk/docutils/docutils/writers/_html_base.py
===================================================================
--- trunk/docutils/docutils/writers/_html_base.py 2026-07-16 10:43:24 UTC (rev 10385)
+++ trunk/docutils/docutils/writers/_html_base.py 2026-07-16 10:43:33 UTC (rev 10386)
@@ -1526,7 +1526,7 @@
atts['classes'].append('external')
else:
assert 'refid' in node, \
- 'References must have "refuri" or "refid" attribute.'
+ f'Reference without "refuri" or "refid" attribute: {node}'
atts['href'] = '#' + node['refid']
atts['classes'].append('internal')
if len(node) == 1 and isinstance(node[0], nodes.image):
Modified: trunk/docutils/test/test_nodes.py
===================================================================
--- trunk/docutils/test/test_nodes.py 2026-07-16 10:43:24 UTC (rev 10385)
+++ trunk/docutils/test/test_nodes.py 2026-07-16 10:43:33 UTC (rev 10386)
@@ -94,6 +94,14 @@
e[0][1] += nodes.Text('some text')
e += nodes.Element()
e += nodes.Element()
+ # Tree Index testlist
+ # <Element> e
+ # <Element> e[0] skip
+ # <Element> e[0][0]
+ # <TextElement> e[0][1] skip
+ # <Text> e[0][1][0]
+ # <Element> e[1] skip
+ # <Element> e[2]
self.testlist = [e[0], e[0][1], e[1]]
compare = [(e, e[0][0]),
(e[0], e[0][0]),
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|