From 1a33ed10b6422e800997ad1ea279db60b4dbb394 Mon Sep 17 00:00:00 2001 From: Tim Nordell Date: Mon, 11 May 2026 16:21:37 -0500 Subject: [PATCH 1/2] [fix] Fix cspan/rspan iterator in flat-table to be stable across removals Iterating through a list while you're modifying it results an indeterminate iteration. Instead, copy the original list for the iterator. An example which doesn't work in Python 3.14 is having this in your flat-table: .. flat-table:: * - :rspan:`1`:cspan:`1` versus the following which works in Python 3.14: .. flat-table:: * - :rspan:`1` :cspan:`1` The difference is the latter ends up with a node inserted between the rspan and cspan. Signed-off-by: Tim Nordell --- linuxdoc/rstFlatTable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linuxdoc/rstFlatTable.py b/linuxdoc/rstFlatTable.py index b05bc4e..65310be 100755 --- a/linuxdoc/rstFlatTable.py +++ b/linuxdoc/rstFlatTable.py @@ -327,7 +327,7 @@ def parseCellItem(self, cellItem): cspan = rspan = 0 if not len(cellItem): # pylint: disable=len-as-condition return cspan, rspan, [] - for elem in cellItem[0]: + for elem in cellItem[0][:]: if isinstance(elem, colSpan): cspan = elem.get("span") elem.parent.remove(elem) From 6beb8f4af42576946c451be61280b22b3b3ee00a Mon Sep 17 00:00:00 2001 From: Tim Nordell Date: Mon, 11 May 2026 16:22:51 -0500 Subject: [PATCH 2/2] [mod] In flat-table remove whitespace surrounding cspan/rspan nodes This permits a flat-table to have a leading paragraph like this: .. flat-table:: * - :rspan:`1` My content here without injecting an extraneous paragraph on the first line. This syntax is useful for certain situations where the content comes from a nested directive like: .. flat-table:: * - :rspan:`1` .. directive:: My directive content here Otherwise the flat-table instance ends up injecting an extra empty paragraph in this situation. The other place that ends up with extra space in the LaTeX output is the following situation: .. flat-table:: * - :rspan:`1` My text here This is also solved by trimming leading whitespace on the following sibling of the element removed. Examples of trimming in the upstream docutils repository can be seen in [1]. [1] https://github.com/docutils/docutils/blob/c8e6032183c603d1b95fed922952437b24f97f1b/docutils/docutils/transforms/references.py#L734-L745 Signed-off-by: Tim Nordell --- linuxdoc/rstFlatTable.py | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/linuxdoc/rstFlatTable.py b/linuxdoc/rstFlatTable.py index 65310be..2ed745c 100755 --- a/linuxdoc/rstFlatTable.py +++ b/linuxdoc/rstFlatTable.py @@ -328,14 +328,32 @@ def parseCellItem(self, cellItem): if not len(cellItem): # pylint: disable=len-as-condition return cspan, rspan, [] for elem in cellItem[0][:]: - if isinstance(elem, colSpan): - cspan = elem.get("span") - elem.parent.remove(elem) - continue - if isinstance(elem, rowSpan): - rspan = elem.get("span") - elem.parent.remove(elem) + if isinstance(elem, colSpan) or isinstance(elem, rowSpan): + if isinstance(elem, colSpan): + cspan = elem.get("span") + if isinstance(elem, rowSpan): + rspan = elem.get("span") + + # Trim leading whitespace in the following element + parent = elem.parent + if parent.index(elem) == 0 and len(parent) > 1: + sibling = parent[1] + if isinstance(sibling, nodes.Text): + new_text = sibling.lstrip() + if len(new_text) != 0: + parent.replace(sibling, nodes.Text(new_text)) + else: + parent.remove(sibling) + + # Finally remove target element from parent + parent.remove(elem) + + # And if the parent paragraph is now empty, remove it + if len(parent) == 0: + parent.replace_self([]) + continue + return cspan, rspan, cellItem[:]