Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions Doc/library/xml.etree.elementtree.rst
Original file line number Diff line number Diff line change
Expand Up @@ -594,38 +594,47 @@ Functions


.. function:: tostring(element, encoding="us-ascii", method="xml", *, \
xml_declaration=None, default_namespace=None,
short_empty_elements=True)

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"``).
*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* and *short_empty_elements* has the same
meaning as in :meth:`ElementTree.write`. Returns an (optionally) encoded string
containing the XML data.

.. versionadded:: 3.4
The *short_empty_elements* parameter.

.. versionadded:: 3.8
The *xml_declaration* and *default_namespace* parameters.


.. function:: tostringlist(element, encoding="us-ascii", method="xml", *, \
xml_declaration=None, default_namespace=None,
short_empty_elements=True)
Comment thread
codeape2 marked this conversation as resolved.

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"``).
*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, except that
``b"".join(tostringlist(element)) == tostring(element)``.
*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,
except that ``b"".join(tostringlist(element)) == tostring(element)``.

.. versionadded:: 3.2

.. versionadded:: 3.4
The *short_empty_elements* parameter.

.. versionadded:: 3.8
The *xml_declaration* and *default_namespace* parameters.


.. function:: XML(text, parser=None)

Expand Down
123 changes: 123 additions & 0 deletions Lib/test/test_xml_etree.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import functools
import html
import io
import locale
import operator
import pickle
import sys
Expand Down Expand Up @@ -756,6 +757,128 @@ def test_writestring(self):
elem = ET.fromstring("<html><body>text</body></html>")
self.assertEqual(ET.tostring(elem), b'<html><body>text</body></html>')

def test_tostring_default_namespace(self):
elem = ET.XML('<body xmlns="http://effbot.org/ns"><tag/></body>')
self.assertEqual(
ET.tostring(elem, encoding='unicode'),
'<ns0:body xmlns:ns0="http://effbot.org/ns"><ns0:tag /></ns0:body>'
)
self.assertEqual(
ET.tostring(elem, encoding='unicode', default_namespace='http://effbot.org/ns'),
Comment thread
codeape2 marked this conversation as resolved.
'<body xmlns="http://effbot.org/ns"><tag /></body>'
)

def test_tostring_default_namespace_different_namespace(self):
elem = ET.XML('<body xmlns="http://effbot.org/ns"><tag/></body>')
self.assertEqual(
ET.tostring(elem, encoding='unicode', default_namespace='foobar'),
'<ns1:body xmlns="foobar" xmlns:ns1="http://effbot.org/ns"><ns1:tag /></ns1:body>'
)

def test_tostring_default_namespace_original_no_namespace(self):
elem = ET.XML('<body><tag/></body>')
EXPECTED_MSG = '^cannot use non-qualified names with default_namespace option$'
with self.assertRaisesRegex(ValueError, EXPECTED_MSG):
ET.tostring(elem, encoding='unicode', default_namespace='foobar')

def test_tostring_no_xml_declaration(self):
elem = ET.XML('<body><tag/></body>')
self.assertEqual(
ET.tostring(elem, encoding='unicode'),
'<body><tag /></body>'
)

def test_tostring_xml_declaration(self):
elem = ET.XML('<body><tag/></body>')
self.assertEqual(
ET.tostring(elem, encoding='utf8', xml_declaration=True),
b"<?xml version='1.0' encoding='utf8'?>\n<body><tag /></body>"
)

def test_tostring_xml_declaration_unicode_encoding(self):
elem = ET.XML('<body><tag/></body>')
preferredencoding = locale.getpreferredencoding()
self.assertEqual(
f"<?xml version='1.0' encoding='{preferredencoding}'?>\n<body><tag /></body>",
ET.tostring(elem, encoding='unicode', xml_declaration=True)
)

def test_tostring_xml_declaration_cases(self):
elem = ET.XML('<body><tag>ø</tag></body>')
preferredencoding = locale.getpreferredencoding()
TESTCASES = [
# (expected_retval, encoding, xml_declaration)
# ... xml_declaration = None
(b'<body><tag>&#248;</tag></body>', None, None),
(b'<body><tag>\xc3\xb8</tag></body>', 'UTF-8', None),
(b'<body><tag>&#248;</tag></body>', 'US-ASCII', None),
(b"<?xml version='1.0' encoding='ISO-8859-1'?>\n"
b"<body><tag>\xf8</tag></body>", 'ISO-8859-1', None),
('<body><tag>ø</tag></body>', 'unicode', None),

# ... xml_declaration = False
(b"<body><tag>&#248;</tag></body>", None, False),
(b"<body><tag>\xc3\xb8</tag></body>", 'UTF-8', False),
(b"<body><tag>&#248;</tag></body>", 'US-ASCII', False),
(b"<body><tag>\xf8</tag></body>", 'ISO-8859-1', False),
("<body><tag>ø</tag></body>", 'unicode', False),

# ... xml_declaration = True
(b"<?xml version='1.0' encoding='us-ascii'?>\n"
b"<body><tag>&#248;</tag></body>", None, True),
(b"<?xml version='1.0' encoding='UTF-8'?>\n"
b"<body><tag>\xc3\xb8</tag></body>", 'UTF-8', True),
(b"<?xml version='1.0' encoding='US-ASCII'?>\n"
b"<body><tag>&#248;</tag></body>", 'US-ASCII', True),
(b"<?xml version='1.0' encoding='ISO-8859-1'?>\n"
b"<body><tag>\xf8</tag></body>", 'ISO-8859-1', True),
(f"<?xml version='1.0' encoding='{preferredencoding}'?>\n"
"<body><tag>ø</tag></body>", 'unicode', True),

]
for expected_retval, encoding, xml_declaration in TESTCASES:
with self.subTest(f'encoding={encoding} '
f'xml_declaration={xml_declaration}'):
self.assertEqual(
ET.tostring(
elem,
encoding=encoding,
xml_declaration=xml_declaration
),
expected_retval
)

def test_tostringlist_default_namespace(self):
elem = ET.XML('<body xmlns="http://effbot.org/ns"><tag/></body>')
self.assertEqual(
''.join(ET.tostringlist(elem, encoding='unicode')),
'<ns0:body xmlns:ns0="http://effbot.org/ns"><ns0:tag /></ns0:body>'
)
self.assertEqual(
''.join(ET.tostringlist(elem, encoding='unicode', default_namespace='http://effbot.org/ns')),
'<body xmlns="http://effbot.org/ns"><tag /></body>'
)

def test_tostringlist_xml_declaration(self):
elem = ET.XML('<body><tag/></body>')
self.assertEqual(
''.join(ET.tostringlist(elem, encoding='unicode')),
'<body><tag /></body>'
)
self.assertEqual(
b''.join(ET.tostringlist(elem, xml_declaration=True)),
b"<?xml version='1.0' encoding='us-ascii'?>\n<body><tag /></body>"
)

preferredencoding = locale.getpreferredencoding()
stringlist = ET.tostringlist(elem, encoding='unicode', xml_declaration=True)
self.assertEqual(
''.join(stringlist),
f"<?xml version='1.0' encoding='{preferredencoding}'?>\n<body><tag /></body>"
)
self.assertRegex(stringlist[0], r"^<\?xml version='1.0' encoding='.+'?>")
self.assertEqual(['<body', '>', '<tag', ' />', '</body>'], stringlist[1:])

def test_encoding(self):
def check(encoding, body=''):
xml = ("<?xml version='1.0' encoding='%s'?><xml>%s</xml>" %
Expand Down
15 changes: 12 additions & 3 deletions Lib/xml/etree/ElementTree.py
Original file line number Diff line number Diff line change
Expand Up @@ -1116,6 +1116,7 @@ def _escape_attrib_html(text):
# --------------------------------------------------------------------

def tostring(element, encoding=None, method=None, *,
xml_declaration=None, default_namespace=None,
short_empty_elements=True):
"""Generate string representation of XML element.

Expand All @@ -1124,13 +1125,17 @@ 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", "text" or "c14n".
be one of "xml" (default), "html", "text" or "c14n", *default_namespace*
sets the default XML namespace (for "xmlns").

Returns an (optionally) encoded string containing the XML data.

"""
stream = io.StringIO() if encoding == 'unicode' else io.BytesIO()
ElementTree(element).write(stream, encoding, method=method,
ElementTree(element).write(stream, encoding,
xml_declaration=xml_declaration,
default_namespace=default_namespace,
method=method,
short_empty_elements=short_empty_elements)
return stream.getvalue()

Expand All @@ -1152,10 +1157,14 @@ def tell(self):
return len(self.lst)

def tostringlist(element, encoding=None, method=None, *,
xml_declaration=None, default_namespace=None,
short_empty_elements=True):
lst = []
stream = _ListDataStream(lst)
ElementTree(element).write(stream, encoding, method=method,
ElementTree(element).write(stream, encoding,
xml_declaration=xml_declaration,
default_namespace=default_namespace,
method=method,
short_empty_elements=short_empty_elements)
return lst

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Added support for keyword arguments `default_namespace` and `xml_declaration` in functions
ElementTree.tostring() and ElementTree.tostringlist().