From b944534c46e17da2f4feed3db1b49e44f1f12345 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 30 Aug 2026 16:54:39 +0300 Subject: [PATCH 1/3] gh-156658: Only XML white space characters are treated as white space XML defines white space as " \t\r\n" (see XML 1.0, 2.3), but str.strip() also strips other characters, such as U+00A0. Such characters could be lost in ElementTree.indent(), in canonicalize(strip_text=True), and when parsing with the whitespace-in-element-content feature turned off. --- Lib/test/test_minidom.py | 24 +++++++++++++++++++ Lib/test/test_xml_etree.py | 14 +++++++++++ Lib/xml/dom/expatbuilder.py | 6 +++-- Lib/xml/dom/minidom.py | 5 +++- Lib/xml/etree/ElementTree.py | 13 ++++++---- ...-08-30-19-30-00.gh-issue-156658.Xq5Nt7.rst | 5 ++++ 6 files changed, 59 insertions(+), 8 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-08-30-19-30-00.gh-issue-156658.Xq5Nt7.rst diff --git a/Lib/test/test_minidom.py b/Lib/test/test_minidom.py index 3735a6046891ea..e204bdc7dc672d 100644 --- a/Lib/test/test_minidom.py +++ b/Lib/test/test_minidom.py @@ -642,6 +642,30 @@ def test_toprettyxml_preserves_content_of_text_node(self): dom.getElementsByTagName('B')[0].childNodes[0].toxml(), dom2.getElementsByTagName('B')[0].childNodes[0].toxml()) + def test_isWhitespaceInElementContent(self): + # only " \t\r\n" are whitespace in XML (see XML 1.0, 2.3) + dom = parseString(']>' + ' x\xa0') + children = dom.documentElement.childNodes + self.assertTrue(children[0].isWhitespaceInElementContent) + self.assertFalse(children[2].isWhitespaceInElementContent) + dom.unlink() + + def test_remove_whitespace_in_element_content(self): + from xml.dom.xmlbuilder import DOMBuilder, DOMInputSource + builder = DOMBuilder() + builder.setFeature("whitespace-in-element-content", False) + source = DOMInputSource() + source.byteStream = io.BytesIO( + b']>' + b' x\xc2\xa0') + dom = builder.parse(source) + children = dom.documentElement.childNodes + # ignorable whitespace is removed, other characters are not + self.assertEqual([node.nodeName for node in children], ['b', '#text']) + self.assertEqual(children[1].data, '\xa0') + dom.unlink() + def testProcessingInstruction(self): dom = parseString('') pi = dom.documentElement.firstChild diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py index 2af2d1fd64520b..f87565a24ee412 100644 --- a/Lib/test/test_xml_etree.py +++ b/Lib/test/test_xml_etree.py @@ -845,6 +845,15 @@ def test_indent_space_caching(self): len({id(el.tail) for el in elem.iter()}), ) + def test_indent_non_xml_whitespace(self): + # only " \t\r\n" are whitespace in XML (see XML 1.0, 2.3) + elem = ET.XML('\xa0

text

\xa0') + ET.indent(elem) + self.assertEqual( + ET.tostring(elem), + b' \n

text

 \n' + ) + def test_indent_level(self): elem = ET.XML("

pre
post

text

") with self.assertRaises(ValueError): @@ -4755,6 +4764,11 @@ def test_simple_roundtrip(self): xml = '' self.assertEqual(c14n_roundtrip(xml), xml) + def test_c14n_strip_non_xml_whitespace(self): + # only " \t\r\n" are whitespace in XML (see XML 1.0, 2.3) + self.assertEqual(c14n_roundtrip(" \xa0x\xa0 ", strip_text=True), + "\xa0x\xa0") + def test_c14n_exclusion(self): xml = textwrap.dedent("""\ diff --git a/Lib/xml/dom/expatbuilder.py b/Lib/xml/dom/expatbuilder.py index d56b2ddfdb2569..e3917c1cc68288 100644 --- a/Lib/xml/dom/expatbuilder.py +++ b/Lib/xml/dom/expatbuilder.py @@ -30,7 +30,8 @@ from xml.dom import xmlbuilder, minidom, Node from xml.dom import EMPTY_NAMESPACE, EMPTY_PREFIX, XMLNS_NAMESPACE from xml.parsers import expat -from xml.dom.minidom import _append_child, _set_attribute_node +from xml.dom.minidom import (_append_child, _set_attribute_node, + _XML_WHITESPACE) from xml.dom.NodeFilter import NodeFilter TEXT_NODE = Node.TEXT_NODE @@ -413,7 +414,8 @@ def _handle_white_text_nodes(self, node, info): # whitespace. L = [] for child in node.childNodes: - if child.nodeType == TEXT_NODE and not child.data.strip(): + if (child.nodeType == TEXT_NODE + and not child.data.strip(_XML_WHITESPACE)): L.append(child) # Remove ignorable whitespace from the tree. diff --git a/Lib/xml/dom/minidom.py b/Lib/xml/dom/minidom.py index 5fd3911bd3c9eb..7639fa14c5050f 100644 --- a/Lib/xml/dom/minidom.py +++ b/Lib/xml/dom/minidom.py @@ -31,6 +31,9 @@ _nodeTypes_with_children = (xml.dom.Node.ELEMENT_NODE, xml.dom.Node.ENTITY_REFERENCE_NODE) +# The white space characters of the XML specification (see XML 1.0, 2.3). +_XML_WHITESPACE = " \t\r\n" + class Node(xml.dom.Node): namespaceURI = None # this is non-null only for elements and attributes @@ -1209,7 +1212,7 @@ def replaceWholeText(self, content): return None def _get_isWhitespaceInElementContent(self): - if self.data.strip(): + if self.data.strip(_XML_WHITESPACE): return False elem = _get_containing_element(self) if elem is None: diff --git a/Lib/xml/etree/ElementTree.py b/Lib/xml/etree/ElementTree.py index 951540eb9f45e9..93b8eceb157161 100644 --- a/Lib/xml/etree/ElementTree.py +++ b/Lib/xml/etree/ElementTree.py @@ -101,6 +101,9 @@ from . import ElementPath +# The white space characters of the XML specification (see XML 1.0, 2.3). +_XML_WHITESPACE = " \t\r\n" + class ParseError(SyntaxError): """An error when parsing an XML document. @@ -1190,17 +1193,17 @@ def _indent_children(elem, level): child_indentation = indentations[level] + space indentations.append(child_indentation) - if not elem.text or not elem.text.strip(): + if not elem.text or not elem.text.strip(_XML_WHITESPACE): elem.text = child_indentation for child in elem: if len(child): _indent_children(child, child_level) - if not child.tail or not child.tail.strip(): + if not child.tail or not child.tail.strip(_XML_WHITESPACE): child.tail = child_indentation # Dedent after the last child by overwriting the previous indentation. - if not child.tail.strip(): + if not child.tail.strip(_XML_WHITESPACE): child.tail = indentations[level] _indent_children(tree, 0) @@ -1705,7 +1708,7 @@ def _default(self, text): if prefix == ">": self._doctype = None return - text = text.strip() + text = text.strip(_XML_WHITESPACE) if not text: return self._doctype.append(text) @@ -1921,7 +1924,7 @@ def _flush(self, _join_text=''.join): data = _join_text(self._data) del self._data[:] if self._strip_text and not self._preserve_space[-1]: - data = data.strip() + data = data.strip(_XML_WHITESPACE) if self._pending_start is not None: args, self._pending_start = self._pending_start, None qname_text = data if data and _looks_like_prefix_name(data) else None diff --git a/Misc/NEWS.d/next/Library/2026-08-30-19-30-00.gh-issue-156658.Xq5Nt7.rst b/Misc/NEWS.d/next/Library/2026-08-30-19-30-00.gh-issue-156658.Xq5Nt7.rst new file mode 100644 index 00000000000000..3f2f105116c183 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-30-19-30-00.gh-issue-156658.Xq5Nt7.rst @@ -0,0 +1,5 @@ +:mod:`xml.dom` and :mod:`xml.etree.ElementTree` no longer treat characters +which are not white space in XML (such as U+00A0) as white space. Previously +they could be lost in :func:`~xml.etree.ElementTree.indent`, +:func:`~xml.etree.ElementTree.canonicalize` with ``strip_text=True``, and when +parsing with the ``whitespace-in-element-content`` feature turned off. From 37616fa57a89d068acc3b66e4de1dcd03eaf378b Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 30 Aug 2026 16:43:01 +0300 Subject: [PATCH 2/3] gh-81623: Do not add whitespace to significant content in toprettyxml() Whitespace is no longer added inside an element which is marked with xml:space="preserve", which is declared in the DTD as not having element content, or, in absence of such declaration, which contains text. Previously such indentation changed the content of the element. --- Doc/library/xml.dom.minidom.rst | 10 ++++ Doc/whatsnew/3.16.rst | 15 +++++ Lib/test/test_minidom.py | 60 ++++++++++++++++--- Lib/xml/dom/minidom.py | 23 +++++++ ...6-08-30-18-00-00.gh-issue-81623.Vh2Kt6.rst | 5 ++ 5 files changed, 105 insertions(+), 8 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-08-30-18-00-00.gh-issue-81623.Vh2Kt6.rst diff --git a/Doc/library/xml.dom.minidom.rst b/Doc/library/xml.dom.minidom.rst index efc81f31e36a5b..eb4984e2b53d92 100644 --- a/Doc/library/xml.dom.minidom.rst +++ b/Doc/library/xml.dom.minidom.rst @@ -187,6 +187,12 @@ module documentation. This section lists the differences between the API and The *standalone* argument behaves exactly as in :meth:`writexml`. + No indentation is added inside an element + which is marked with ``xml:space="preserve"``, + which is declared in the DTD as not having element content, + or, in absence of such declaration, which contains text, + because this would change its content. + .. versionchanged:: 3.8 The :meth:`toprettyxml` method now preserves the attribute order specified by the user. @@ -194,6 +200,10 @@ module documentation. This section lists the differences between the API and .. versionchanged:: 3.9 The *standalone* parameter was added. + .. versionchanged:: next + Whitespace is no longer added inside an element with mixed content + or marked with ``xml:space="preserve"``. + .. _dom-example: DOM Example diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index f3ddae7a2b2fdd..39c75033f05f41 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -642,6 +642,13 @@ xml and :meth:`!Document.createEntityReference`. (Contributed by Jason Orendorff and Serhiy Storchaka in :gh:`44871`.) +* :meth:`~xml.dom.minidom.Node.toprettyxml` in :mod:`xml.dom.minidom` + no longer adds whitespace inside an element + which is marked with ``xml:space="preserve"``, + which is declared in the DTD as not having element content, + or, in absence of such declaration, which contains text. + (Contributed by Serhiy Storchaka in :gh:`81623`.) + * Add :meth:`!GetSpecifiedAttributeCount` method to the :mod:`XML parser ` objects. It tells how many of the reported attributes were given in the start tag @@ -881,6 +888,14 @@ that may require changes to your code. Attributes defaulted in the DTD are no longer omitted when parsing. (Contributed by Jason Orendorff and Serhiy Storchaka in :gh:`44871`.) +* :meth:`~xml.dom.minidom.Node.toprettyxml` in :mod:`xml.dom.minidom` + no longer adds whitespace inside an element + which is marked with ``xml:space="preserve"``, + which is declared in the DTD as not having element content, + or, in absence of such declaration, which contains text, + because this changed the content of the element. + (Contributed by Serhiy Storchaka in :gh:`81623`.) + * On Windows, seeking a pipe now fails instead of silently appearing to succeed: :func:`os.lseek` and :meth:`~io.IOBase.seek` raise :exc:`OSError`, and :meth:`~io.IOBase.seekable` returns ``False``. As a consequence, diff --git a/Lib/test/test_minidom.py b/Lib/test/test_minidom.py index e204bdc7dc672d..0d05a680608669 100644 --- a/Lib/test/test_minidom.py +++ b/Lib/test/test_minidom.py @@ -609,32 +609,76 @@ def testAltNewline(self): self.assertEqual(domstr, str.replace("\n", "\r\n")) def test_toprettyxml_with_text_nodes(self): - # see issue #4147, text nodes are not indented + # see gh-48397 and gh-81623, + # the content of an element with text is not changed decl = '\n' self.assertEqual(parseString('A').toprettyxml(), decl + 'A\n') self.assertEqual(parseString('AA').toprettyxml(), - decl + '\n\tA\n\tA\n\n') + decl + 'AA\n') self.assertEqual(parseString('AA').toprettyxml(), - decl + '\n\tA\n\tA\n\n') + decl + 'AA\n') self.assertEqual(parseString('AA').toprettyxml(), decl + '\n\tA\n\tA\n\n') self.assertEqual(parseString('AAA').toprettyxml(), - decl + '\n\tA\n\tA\n\tA\n\n') + decl + 'AAA\n') + # toprettyxml treats whitespace between elements as insignificant + self.assertEqual(parseString(' A ').toprettyxml(), + decl + '\n\t \n\tA\n\t \n\n') def test_toprettyxml_with_adjacent_text_nodes(self): - # see issue #4147, adjacent text nodes are indented normally + # see gh-81623, adjacent text nodes are not separated dom = Document() elem = dom.createElement('elem') elem.appendChild(dom.createTextNode('TEXT')) elem.appendChild(dom.createTextNode('TEXT')) dom.appendChild(elem) decl = '\n' - self.assertEqual(dom.toprettyxml(), - decl + '\n\tTEXT\n\tTEXT\n\n') + self.assertEqual(dom.toprettyxml(), decl + 'TEXTTEXT\n') + + def test_toprettyxml_preserve(self): + decl = '\n' + # xml:space="preserve" applies to the whole subtree + self.assertEqual( + parseString('AA' + ).toprettyxml(), + decl + 'AA\n') + self.assertEqual( + parseString('' + ).toprettyxml(), + decl + '\n') + # other values do not preserve whitespace + self.assertEqual( + parseString('A').toprettyxml(), + decl + '\n\tA\n\n') + + def test_toprettyxml_with_non_xml_whitespace(self): + # only " \t\r\n" are whitespace in XML (see XML 1.0, 2.3) + decl = '\n' + self.assertEqual(parseString('\xa0A').toprettyxml(), + decl + '\xa0A\n') + + def test_toprettyxml_with_dtd(self): + decl = '\n' + # only whitespace in element content is ignorable + doctype = ('' + ']>') + self.assertEqual( + parseString(doctype + 'AA').toprettyxml(), + decl + doctype + '\nAA\n') + doctype = ']>' + self.assertEqual( + parseString(doctype + 'AA').toprettyxml(), + decl + doctype + '\n\n\tA\n\tA\n\n') + + def test_toprettyxml_with_cdata_section(self): + decl = '\n' + self.assertEqual( + parseString('A').toprettyxml(), + decl + 'A\n') def test_toprettyxml_preserves_content_of_text_node(self): - # see issue #4147 + # see gh-48397 for str in ('A', 'C'): dom = parseString(str) dom2 = parseString(dom.toprettyxml()) diff --git a/Lib/xml/dom/minidom.py b/Lib/xml/dom/minidom.py index 7639fa14c5050f..93c2e0638493e3 100644 --- a/Lib/xml/dom/minidom.py +++ b/Lib/xml/dom/minidom.py @@ -940,6 +940,10 @@ def writexml(self, writer, indent="", addindent="", newl=""): self.childNodes[0].nodeType in ( Node.TEXT_NODE, Node.CDATA_SECTION_NODE)): self.childNodes[0].writexml(writer, '', '', '') + elif self._preserves_whitespace(): + # Adding whitespace here would change the content. + for node in self.childNodes: + node.writexml(writer, '', '', '') else: writer.write(newl) for node in self.childNodes: @@ -949,6 +953,25 @@ def writexml(self, writer, indent="", addindent="", newl=""): else: writer.write("/>%s"%(newl)) + def _preserves_whitespace(self): + """Returns true iff whitespace in the content is significant. + + This is the case if the element is marked with xml:space="preserve", + if the DTD declares that its content model is not element content, + or, in absence of such declaration, if it contains text. + """ + if self.getAttribute("xml:space") == "preserve": + return True + doc = self.ownerDocument + info = doc and doc._get_elem_info(self) + if info is not None: + # Only whitespace in element content is ignorable + # (see XML 1.0, 3.2.1). + return not info.isElementContent() + return any(node.nodeType in (Node.TEXT_NODE, Node.CDATA_SECTION_NODE) + and node.data.strip(_XML_WHITESPACE) + for node in self.childNodes) + def _get_attributes(self): self._ensure_attributes() return NamedNodeMap(self._attrs, self._attrsNS, self) diff --git a/Misc/NEWS.d/next/Library/2026-08-30-18-00-00.gh-issue-81623.Vh2Kt6.rst b/Misc/NEWS.d/next/Library/2026-08-30-18-00-00.gh-issue-81623.Vh2Kt6.rst new file mode 100644 index 00000000000000..1964ff74e99c04 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-30-18-00-00.gh-issue-81623.Vh2Kt6.rst @@ -0,0 +1,5 @@ +:meth:`~xml.dom.minidom.Node.toprettyxml` in :mod:`xml.dom.minidom` no longer +adds whitespace inside an element which is marked with +``xml:space="preserve"``, which is declared in the DTD as not having element +content, or, in absence of such declaration, which contains text. Previously +such indentation changed the content of the element. From 159e84a882d9b60371884591fc04b8ba4e4251eb Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 30 Aug 2026 17:17:07 +0300 Subject: [PATCH 3/3] gh-81623: Do not add whitespace to significant content in indent() ElementTree.indent() only avoided overwriting text and tails which are not white space, but in an element which contains text the white space between them is significant too. xml:space="preserve" is now honored as well. --- Doc/library/xml.etree.elementtree.rst | 8 +++++ Doc/whatsnew/3.16.rst | 18 +++++----- Lib/test/test_xml_etree.py | 35 +++++++++++++++++-- Lib/xml/etree/ElementTree.py | 25 +++++++++---- ...6-08-30-18-00-00.gh-issue-81623.Vh2Kt6.rst | 11 +++--- 5 files changed, 76 insertions(+), 21 deletions(-) diff --git a/Doc/library/xml.etree.elementtree.rst b/Doc/library/xml.etree.elementtree.rst index 310ccd651e18c7..e06620bd12d6ee 100644 --- a/Doc/library/xml.etree.elementtree.rst +++ b/Doc/library/xml.etree.elementtree.rst @@ -603,8 +603,16 @@ Functions characters by default. For indenting partial subtrees inside of an already indented tree, pass the initial indentation level as *level*. + No whitespace is added inside an element + which is marked with ``xml:space="preserve"`` + or which contains text, because this would change its content. + .. versionadded:: 3.9 + .. versionchanged:: next + Whitespace is no longer added inside an element with mixed content + or marked with ``xml:space="preserve"``. + .. function:: iselement(element) diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index 39c75033f05f41..274bf4edc50543 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -643,10 +643,11 @@ xml (Contributed by Jason Orendorff and Serhiy Storchaka in :gh:`44871`.) * :meth:`~xml.dom.minidom.Node.toprettyxml` in :mod:`xml.dom.minidom` - no longer adds whitespace inside an element - which is marked with ``xml:space="preserve"``, - which is declared in the DTD as not having element content, - or, in absence of such declaration, which contains text. + and :func:`~xml.etree.ElementTree.indent` in :mod:`xml.etree.ElementTree` + no longer add whitespace inside an element + which is marked with ``xml:space="preserve"`` or which contains text. + :meth:`!toprettyxml` also takes into account + the content model declared in the DTD. (Contributed by Serhiy Storchaka in :gh:`81623`.) * Add :meth:`!GetSpecifiedAttributeCount` method @@ -889,11 +890,12 @@ that may require changes to your code. (Contributed by Jason Orendorff and Serhiy Storchaka in :gh:`44871`.) * :meth:`~xml.dom.minidom.Node.toprettyxml` in :mod:`xml.dom.minidom` - no longer adds whitespace inside an element - which is marked with ``xml:space="preserve"``, - which is declared in the DTD as not having element content, - or, in absence of such declaration, which contains text, + and :func:`~xml.etree.ElementTree.indent` in :mod:`xml.etree.ElementTree` + no longer add whitespace inside an element + which is marked with ``xml:space="preserve"`` or which contains text, because this changed the content of the element. + :meth:`!toprettyxml` also takes into account + the content model declared in the DTD. (Contributed by Serhiy Storchaka in :gh:`81623`.) * On Windows, seeking a pipe now fails instead of silently appearing to diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py index f87565a24ee412..b4eba6a6bbeef7 100644 --- a/Lib/test/test_xml_etree.py +++ b/Lib/test/test_xml_etree.py @@ -773,9 +773,10 @@ def test_indent(self): ET.indent(elem) self.assertEqual(ET.tostring(elem), b'\n text\n') + # an element with mixed content is not indented elem = ET.XML("texttail") ET.indent(elem) - self.assertEqual(ET.tostring(elem), b'\n texttail') + self.assertEqual(ET.tostring(elem), b'texttail') elem = ET.XML("

par

\n

text

\t


") ET.indent(elem) @@ -851,9 +852,39 @@ def test_indent_non_xml_whitespace(self): ET.indent(elem) self.assertEqual( ET.tostring(elem), - b' \n

text

 \n' + b' 

text

 ' ) + def test_indent_preserve(self): + # xml:space="preserve" applies to the whole subtree + elem = ET.XML('

text

') + ET.indent(elem) + self.assertEqual( + ET.tostring(elem), + b'

text

' + ) + # other values do not preserve whitespace + elem = ET.XML('

text

') + ET.indent(elem) + self.assertEqual( + ET.tostring(elem), + b'\n' + b' \n' + b'

text

\n' + b' \n' + b'' + ) + + def test_indent_mixed_content(self): + # whitespace in an element which contains text is significant + elem = ET.XML('

hello x y

') + ET.indent(elem) + self.assertEqual(ET.tostring(elem), b'

hello x y

') + # the subtree of such element is not indented either + elem = ET.XML('

hello y

') + ET.indent(elem) + self.assertEqual(ET.tostring(elem), b'

hello y

') + def test_indent_level(self): elem = ET.XML("

pre
post

text

") with self.assertRaises(ValueError): diff --git a/Lib/xml/etree/ElementTree.py b/Lib/xml/etree/ElementTree.py index 93b8eceb157161..07faa3e42aef49 100644 --- a/Lib/xml/etree/ElementTree.py +++ b/Lib/xml/etree/ElementTree.py @@ -104,6 +104,9 @@ # The white space characters of the XML specification (see XML 1.0, 2.3). _XML_WHITESPACE = " \t\r\n" +# The xml:space attribute (see XML 1.0, 2.10). +_XML_SPACE = "{http://www.w3.org/XML/1998/namespace}space" + class ParseError(SyntaxError): """An error when parsing an XML document. @@ -1184,7 +1187,20 @@ def indent(tree, space=" ", level=0): # Reduce the memory consumption by reusing indentation strings. indentations = ["\n" + level * space] + def _preserves_whitespace(elem): + # True iff whitespace in the content of the element is significant. + if elem.get(_XML_SPACE) == "preserve": + return True + if elem.text and elem.text.strip(_XML_WHITESPACE): + return True + return any(child.tail and child.tail.strip(_XML_WHITESPACE) + for child in elem) + def _indent_children(elem, level): + if _preserves_whitespace(elem): + # Adding whitespace here would change the content. + return + # Start a new indentation level for the first child. child_level = level + 1 try: @@ -1193,18 +1209,15 @@ def _indent_children(elem, level): child_indentation = indentations[level] + space indentations.append(child_indentation) - if not elem.text or not elem.text.strip(_XML_WHITESPACE): - elem.text = child_indentation + elem.text = child_indentation for child in elem: if len(child): _indent_children(child, child_level) - if not child.tail or not child.tail.strip(_XML_WHITESPACE): - child.tail = child_indentation + child.tail = child_indentation # Dedent after the last child by overwriting the previous indentation. - if not child.tail.strip(_XML_WHITESPACE): - child.tail = indentations[level] + child.tail = indentations[level] _indent_children(tree, 0) diff --git a/Misc/NEWS.d/next/Library/2026-08-30-18-00-00.gh-issue-81623.Vh2Kt6.rst b/Misc/NEWS.d/next/Library/2026-08-30-18-00-00.gh-issue-81623.Vh2Kt6.rst index 1964ff74e99c04..bdc02a4c55ee03 100644 --- a/Misc/NEWS.d/next/Library/2026-08-30-18-00-00.gh-issue-81623.Vh2Kt6.rst +++ b/Misc/NEWS.d/next/Library/2026-08-30-18-00-00.gh-issue-81623.Vh2Kt6.rst @@ -1,5 +1,6 @@ -:meth:`~xml.dom.minidom.Node.toprettyxml` in :mod:`xml.dom.minidom` no longer -adds whitespace inside an element which is marked with -``xml:space="preserve"``, which is declared in the DTD as not having element -content, or, in absence of such declaration, which contains text. Previously -such indentation changed the content of the element. +:meth:`~xml.dom.minidom.Node.toprettyxml` in :mod:`xml.dom.minidom` and +:func:`~xml.etree.ElementTree.indent` in :mod:`xml.etree.ElementTree` no longer +add whitespace inside an element which is marked with ``xml:space="preserve"`` +or which contains text (:meth:`!toprettyxml` also takes into account the +content model declared in the DTD). Previously such indentation changed the +content of the element.