diff options
-rw-r--r-- | Doc/library/xml.etree.elementtree.rst | 21 | ||||
-rw-r--r-- | Lib/test/test_xml_etree.py | 123 | ||||
-rw-r--r-- | Lib/xml/etree/ElementTree.py | 15 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Library/2019-03-07-20-02-18.bpo-36227.i2Z1XR.rst | 2 |
4 files changed, 152 insertions, 9 deletions
diff --git a/Doc/library/xml.etree.elementtree.rst b/Doc/library/xml.etree.elementtree.rst index 4a7cf6f095..9bee0eadc2 100644 --- a/Doc/library/xml.etree.elementtree.rst +++ b/Doc/library/xml.etree.elementtree.rst @@ -594,6 +594,7 @@ 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 @@ -601,14 +602,19 @@ Functions 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) Generates a string representation of an XML element, including all @@ -616,16 +622,19 @@ Functions 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) diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py index 8a7ec0076f..bdcd4e0d19 100644 --- a/Lib/test/test_xml_etree.py +++ b/Lib/test/test_xml_etree.py @@ -9,6 +9,7 @@ import copy import functools import html import io +import locale import operator import pickle import sys @@ -756,6 +757,128 @@ class ElementTreeTest(unittest.TestCase): 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'), + '<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>ø</tag></body>', None, None), + (b'<body><tag>\xc3\xb8</tag></body>', 'UTF-8', None), + (b'<body><tag>ø</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>ø</tag></body>", None, False), + (b"<body><tag>\xc3\xb8</tag></body>", 'UTF-8', False), + (b"<body><tag>ø</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>ø</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>ø</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>" % diff --git a/Lib/xml/etree/ElementTree.py b/Lib/xml/etree/ElementTree.py index b5ad8e1d14..c9e2f36835 100644 --- a/Lib/xml/etree/ElementTree.py +++ b/Lib/xml/etree/ElementTree.py @@ -1113,6 +1113,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. @@ -1121,13 +1122,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() @@ -1149,10 +1154,14 @@ class _ListDataStream(io.BufferedIOBase): 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 diff --git a/Misc/NEWS.d/next/Library/2019-03-07-20-02-18.bpo-36227.i2Z1XR.rst b/Misc/NEWS.d/next/Library/2019-03-07-20-02-18.bpo-36227.i2Z1XR.rst new file mode 100644 index 0000000000..3b5b6cda09 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-03-07-20-02-18.bpo-36227.i2Z1XR.rst @@ -0,0 +1,2 @@ +Added support for keyword arguments `default_namespace` and `xml_declaration` in functions +ElementTree.tostring() and ElementTree.tostringlist(). |