https://github.com/python/cpython/commit/532b9dbd678b6d8e51d4199db8bd7d6b157fdea4
commit: 532b9dbd678b6d8e51d4199db8bd7d6b157fdea4
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-09-01T18:01:47+03:00
summary:
gh-89499: Support the standalone document declaration in ElementTree (GH-156730)
ElementTree.write(), tostring() and tostringlist() get the standalone
parameter: True for standalone="yes", False for standalone="no", and None
(the default) to omit it. An XML declaration is written if it is not None,
and combining it with a false xml_declaration is an error.
files:
A Misc/NEWS.d/next/Library/2026-08-31-19-30-00.gh-issue-89499.Kx8vQ1.rst
M Doc/library/xml.etree.elementtree.rst
M Lib/test/test_xml_etree.py
M Lib/xml/etree/ElementTree.py
diff --git a/Doc/library/xml.etree.elementtree.rst
b/Doc/library/xml.etree.elementtree.rst
index 310ccd651e18c7e..f9ffb07ada88fd3 100644
--- a/Doc/library/xml.etree.elementtree.rst
+++ b/Doc/library/xml.etree.elementtree.rst
@@ -711,16 +711,16 @@ Functions
.. function:: tostring(element, encoding="us-ascii", method="xml", *, \
xml_declaration=None, default_namespace=None, \
- short_empty_elements=True)
+ short_empty_elements=True, standalone=None)
Generates a string representation of an XML element, including all
subelements. *element* is an :class:`Element` instance. *encoding* [1]_ is
the output encoding (default is US-ASCII). Use ``encoding="unicode"`` to
generate a Unicode string (otherwise, a bytestring is generated). *method*
is either ``"xml"``, ``"html"`` or ``"text"`` (default is ``"xml"``).
- *xml_declaration*, *default_namespace* and *short_empty_elements* has the
same
- meaning as in :meth:`ElementTree.write`. Returns an (optionally) encoded
string
- containing the XML data.
+ *xml_declaration*, *default_namespace*, *short_empty_elements* and
+ *standalone* has the same meaning as in :meth:`ElementTree.write`.
+ Returns an (optionally) encoded string containing the XML data.
.. versionchanged:: 3.4
Added the *short_empty_elements* parameter.
@@ -732,19 +732,23 @@ Functions
The :func:`tostring` function now preserves the attribute order
specified by the user.
+ .. versionchanged:: next
+ Added the *standalone* parameter.
+
.. function:: tostringlist(element, encoding="us-ascii", method="xml", *, \
xml_declaration=None, default_namespace=None, \
- short_empty_elements=True)
+ short_empty_elements=True, standalone=None)
Generates a string representation of an XML element, including all
subelements. *element* is an :class:`Element` instance. *encoding* [1]_ is
the output encoding (default is US-ASCII). Use ``encoding="unicode"`` to
generate a Unicode string (otherwise, a bytestring is generated). *method*
is either ``"xml"``, ``"html"`` or ``"text"`` (default is ``"xml"``).
- *xml_declaration*, *default_namespace* and *short_empty_elements* has the
same
- meaning as in :meth:`ElementTree.write`. Returns a list of (optionally)
encoded
- strings containing the XML data. It does not guarantee any specific
sequence,
+ *xml_declaration*, *default_namespace*, *short_empty_elements* and
+ *standalone* has the same meaning as in :meth:`ElementTree.write`.
+ Returns a list of (optionally) encoded strings containing the XML data.
+ It does not guarantee any specific sequence,
except that ``b"".join(tostringlist(element)) == tostring(element)``.
.. versionadded:: 3.2
@@ -759,6 +763,9 @@ Functions
The :func:`tostringlist` function now preserves the attribute order
specified by the user.
+ .. versionchanged:: next
+ Added the *standalone* parameter.
+
.. function:: XML(text, parser=None)
@@ -1186,7 +1193,7 @@ ElementTree Objects
.. method:: write(file, encoding="us-ascii", xml_declaration=None, \
default_namespace=None, method="xml", *, \
- short_empty_elements=True)
+ short_empty_elements=True, standalone=None)
Writes the element tree to a file, as XML. *file* is a file name, or a
:term:`file object` opened for writing. *encoding* [1]_ is the output
@@ -1202,6 +1209,13 @@ ElementTree Objects
emitted as a single self-closed tag, otherwise they are emitted as a pair
of start/end tags.
+ The keyword-only *standalone* parameter is the value of the standalone
+ document declaration in the XML declaration.
+ Use ``True`` for ``standalone="yes"``, ``False`` for ``standalone="no"``,
+ and ``None`` (the default) to omit it.
+ An XML declaration is written if *standalone* is not ``None``;
+ combining it with ``xml_declaration=False`` raises a :exc:`ValueError`.
+
The output is either a string (:class:`str`) or binary (:class:`bytes`).
This is controlled by the *encoding* argument. If *encoding* is
``"unicode"``, the output is a string; otherwise, it's binary. Note that
@@ -1216,6 +1230,9 @@ ElementTree Objects
The :meth:`write` method now preserves the attribute order specified
by the user.
+ .. versionchanged:: next
+ Added the *standalone* parameter.
+
This is the XML file that is going to be manipulated::
diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py
index fae212e7d2d369f..a0337de58b23ae4 100644
--- a/Lib/test/test_xml_etree.py
+++ b/Lib/test/test_xml_etree.py
@@ -970,6 +970,88 @@ def test_tostring_xml_declaration_cases(self):
expected_retval
)
+ def test_tostring_standalone(self):
+ elem = ET.XML('<body><tag/></body>')
+ self.assertEqual(
+ ET.tostring(elem, encoding='unicode', standalone=True),
+ "<?xml version='1.0' encoding='utf-8' standalone='yes'?>\n"
+ "<body><tag /></body>"
+ )
+ self.assertEqual(
+ ET.tostring(elem, encoding='unicode', standalone=False),
+ "<?xml version='1.0' encoding='utf-8' standalone='no'?>\n"
+ "<body><tag /></body>"
+ )
+ # the XML declaration is written even if it would be omitted
+ self.assertEqual(
+ ET.tostring(elem, standalone=True),
+ b"<?xml version='1.0' encoding='us-ascii' standalone='yes'?>\n"
+ b"<body><tag /></body>"
+ )
+ self.assertEqual(
+ ET.tostring(elem, encoding='UTF-8', standalone=False),
+ b"<?xml version='1.0' encoding='UTF-8' standalone='no'?>\n"
+ b"<body><tag /></body>"
+ )
+
+ def test_tostring_standalone_none(self):
+ elem = ET.XML('<body><tag/></body>')
+ self.assertEqual(
+ ET.tostring(elem, encoding='unicode', standalone=None),
+ '<body><tag /></body>'
+ )
+ self.assertEqual(
+ ET.tostring(elem, encoding='unicode', xml_declaration=True,
+ standalone=None),
+ "<?xml version='1.0' encoding='utf-8'?>\n<body><tag /></body>"
+ )
+
+ def test_tostring_standalone_without_xml_declaration(self):
+ elem = ET.XML('<body><tag/></body>')
+ for standalone in True, False:
+ for xml_declaration in False, 0, '':
+ with self.subTest(standalone=standalone,
+ xml_declaration=xml_declaration):
+ with self.assertRaises(ValueError):
+ ET.tostring(elem, xml_declaration=xml_declaration,
+ standalone=standalone)
+
+ def test_tostring_standalone_text_method(self):
+ elem = ET.XML('<body><tag>text</tag></body>')
+ self.assertEqual(
+ ET.tostring(elem, encoding='unicode', method='text',
+ standalone=True),
+ 'text'
+ )
+
+ def test_tostringlist_standalone(self):
+ elem = ET.XML('<body><tag/></body>')
+ self.assertEqual(
+ b''.join(ET.tostringlist(elem, standalone=True)),
+ b"<?xml version='1.0' encoding='us-ascii' standalone='yes'?>\n"
+ b"<body><tag /></body>"
+ )
+ with self.assertRaises(ValueError):
+ ET.tostringlist(elem, xml_declaration=False, standalone=False)
+
+ def test_write_standalone(self):
+ elem = ET.XML('<body><tag/></body>')
+ tree = ET.ElementTree(elem)
+ for standalone, expected in [
+ (True, "standalone='yes'"), (False, "standalone='no'")]:
+ with self.subTest(standalone=standalone):
+ file = io.StringIO()
+ tree.write(file, encoding='unicode', standalone=standalone)
+ self.assertEqual(
+ file.getvalue(),
+ "<?xml version='1.0' encoding='utf-8' %s?>\n"
+ "<body><tag /></body>" % expected
+ )
+ file = io.StringIO()
+ with self.assertRaises(ValueError):
+ tree.write(file, encoding='unicode', xml_declaration=False,
+ standalone=True)
+
def test_tostringlist_default_namespace(self):
elem = ET.XML('<body xmlns="http://effbot.org/ns"><tag/></body>')
self.assertEqual(
diff --git a/Lib/xml/etree/ElementTree.py b/Lib/xml/etree/ElementTree.py
index 951540eb9f45e90..1cba3eae7ad629d 100644
--- a/Lib/xml/etree/ElementTree.py
+++ b/Lib/xml/etree/ElementTree.py
@@ -697,7 +697,8 @@ def write(self, file_or_filename,
xml_declaration=None,
default_namespace=None,
method=None, *,
- short_empty_elements=True):
+ short_empty_elements=True,
+ standalone=None):
"""Write element tree to a file as XML.
Arguments:
@@ -722,6 +723,10 @@ def write(self, file_or_filename,
self-closed tag, otherwise they are
emitted as a pair of start/end tags
+ *standalone* -- bool for the standalone document declaration in
+ the XML declaration. If None (default), the
+ standalone document declaration is omitted
+
"""
if self._root is None:
raise TypeError('ElementTree not initialized')
@@ -729,6 +734,11 @@ def write(self, file_or_filename,
method = "xml"
elif method not in _serialize:
raise ValueError("unknown method %r" % method)
+ if standalone is not None:
+ if xml_declaration is not None and not xml_declaration:
+ raise ValueError("the standalone document declaration "
+ "requires the XML declaration")
+ xml_declaration = True
if not encoding:
encoding = "us-ascii"
with _get_writer(file_or_filename, encoding) as (write,
declared_encoding):
@@ -738,8 +748,13 @@ def write(self, file_or_filename,
(xml_declaration is None and
encoding.lower() != "unicode" and
declared_encoding.lower() not in ("utf-8", "us-ascii"))):
- write("<?xml version='1.0' encoding='%s'?>\n" % (
- declared_encoding,))
+ if standalone is None:
+ sddecl = ""
+ else:
+ sddecl = " standalone='%s'" % (
+ "yes" if standalone else "no",)
+ write("<?xml version='1.0' encoding='%s'%s?>\n" % (
+ declared_encoding, sddecl))
if method == "text":
_serialize_text(write, self._root)
else:
@@ -1085,7 +1100,7 @@ def _escape_attrib_html(text):
def tostring(element, encoding=None, method=None, *,
xml_declaration=None, default_namespace=None,
- short_empty_elements=True):
+ short_empty_elements=True, standalone=None):
"""Generate string representation of XML element.
All subelements are included. If encoding is "unicode", a string
@@ -1094,7 +1109,9 @@ def tostring(element, encoding=None, method=None, *,
*element* is an Element instance, *encoding* is an optional output
encoding defaulting to US-ASCII, *method* is an optional output which
can be one of "xml" (default), "html" or "text",
- *default_namespace* sets the default XML namespace (for "xmlns").
+ *default_namespace* sets the default XML namespace (for "xmlns"),
+ *standalone* is the value of the standalone document declaration
+ in the XML declaration (omitted if None).
Returns an (optionally) encoded string containing the XML data.
@@ -1104,7 +1121,8 @@ def tostring(element, encoding=None, method=None, *,
xml_declaration=xml_declaration,
default_namespace=default_namespace,
method=method,
- short_empty_elements=short_empty_elements)
+ short_empty_elements=short_empty_elements,
+ standalone=standalone)
return stream.getvalue()
class _ListDataStream(io.BufferedIOBase):
@@ -1126,14 +1144,15 @@ def tell(self):
def tostringlist(element, encoding=None, method=None, *,
xml_declaration=None, default_namespace=None,
- short_empty_elements=True):
+ short_empty_elements=True, standalone=None):
lst = []
stream = _ListDataStream(lst)
ElementTree(element).write(stream, encoding,
xml_declaration=xml_declaration,
default_namespace=default_namespace,
method=method,
- short_empty_elements=short_empty_elements)
+ short_empty_elements=short_empty_elements,
+ standalone=standalone)
return lst
diff --git
a/Misc/NEWS.d/next/Library/2026-08-31-19-30-00.gh-issue-89499.Kx8vQ1.rst
b/Misc/NEWS.d/next/Library/2026-08-31-19-30-00.gh-issue-89499.Kx8vQ1.rst
new file mode 100644
index 000000000000000..d0b8ead7506af0a
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-08-31-19-30-00.gh-issue-89499.Kx8vQ1.rst
@@ -0,0 +1,5 @@
+:meth:`ElementTree.write() <xml.etree.ElementTree.ElementTree.write>`,
+:func:`~xml.etree.ElementTree.tostring` and
+:func:`~xml.etree.ElementTree.tostringlist` now support the *standalone*
+parameter, the value of the standalone document declaration
+in the XML declaration.
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]