Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ffca16e

Browse files
codeape2scoder
authored andcommitted
bpo-36227: ElementTree.tostring() default_namespace and xml_declaration arguments (GH-12225)
Add new keyword arguments "default_namespace" and "xml_declaration" to functions ET.tostring() and ET.tostringlist(), as known from ElementTree.write().
1 parent 830b43d commit ffca16e

4 files changed

Lines changed: 152 additions & 9 deletions

File tree

Doc/library/xml.etree.elementtree.rst

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -594,38 +594,47 @@ Functions
594594

595595

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

599600
Generates a string representation of an XML element, including all
600601
subelements. *element* is an :class:`Element` instance. *encoding* [1]_ is
601602
the output encoding (default is US-ASCII). Use ``encoding="unicode"`` to
602603
generate a Unicode string (otherwise, a bytestring is generated). *method*
603604
is either ``"xml"``, ``"html"`` or ``"text"`` (default is ``"xml"``).
604-
*short_empty_elements* has the same meaning as in :meth:`ElementTree.write`.
605-
Returns an (optionally) encoded string containing the XML data.
605+
*xml_declaration*, *default_namespace* and *short_empty_elements* has the same
606+
meaning as in :meth:`ElementTree.write`. Returns an (optionally) encoded string
607+
containing the XML data.
606608

607609
.. versionadded:: 3.4
608610
The *short_empty_elements* parameter.
609611

612+
.. versionadded:: 3.8
613+
The *xml_declaration* and *default_namespace* parameters.
614+
610615

611616
.. function:: tostringlist(element, encoding="us-ascii", method="xml", *, \
617+
xml_declaration=None, default_namespace=None,
612618
short_empty_elements=True)
613619

614620
Generates a string representation of an XML element, including all
615621
subelements. *element* is an :class:`Element` instance. *encoding* [1]_ is
616622
the output encoding (default is US-ASCII). Use ``encoding="unicode"`` to
617623
generate a Unicode string (otherwise, a bytestring is generated). *method*
618624
is either ``"xml"``, ``"html"`` or ``"text"`` (default is ``"xml"``).
619-
*short_empty_elements* has the same meaning as in :meth:`ElementTree.write`.
620-
Returns a list of (optionally) encoded strings containing the XML data.
621-
It does not guarantee any specific sequence, except that
622-
``b"".join(tostringlist(element)) == tostring(element)``.
625+
*xml_declaration*, *default_namespace* and *short_empty_elements* has the same
626+
meaning as in :meth:`ElementTree.write`. Returns a list of (optionally) encoded
627+
strings containing the XML data. It does not guarantee any specific sequence,
628+
except that ``b"".join(tostringlist(element)) == tostring(element)``.
623629

624630
.. versionadded:: 3.2
625631

626632
.. versionadded:: 3.4
627633
The *short_empty_elements* parameter.
628634

635+
.. versionadded:: 3.8
636+
The *xml_declaration* and *default_namespace* parameters.
637+
629638

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

Lib/test/test_xml_etree.py

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import functools
1010
import html
1111
import io
12+
import locale
1213
import operator
1314
import pickle
1415
import sys
@@ -756,6 +757,128 @@ def test_writestring(self):
756757
elem = ET.fromstring("<html><body>text</body></html>")
757758
self.assertEqual(ET.tostring(elem), b'<html><body>text</body></html>')
758759

760+
def test_tostring_default_namespace(self):
761+
elem = ET.XML('<body xmlns="http://effbot.org/ns"><tag/></body>')
762+
self.assertEqual(
763+
ET.tostring(elem, encoding='unicode'),
764+
'<ns0:body xmlns:ns0="http://effbot.org/ns"><ns0:tag /></ns0:body>'
765+
)
766+
self.assertEqual(
767+
ET.tostring(elem, encoding='unicode', default_namespace='http://effbot.org/ns'),
768+
'<body xmlns="http://effbot.org/ns"><tag /></body>'
769+
)
770+
771+
def test_tostring_default_namespace_different_namespace(self):
772+
elem = ET.XML('<body xmlns="http://effbot.org/ns"><tag/></body>')
773+
self.assertEqual(
774+
ET.tostring(elem, encoding='unicode', default_namespace='foobar'),
775+
'<ns1:body xmlns="foobar" xmlns:ns1="http://effbot.org/ns"><ns1:tag /></ns1:body>'
776+
)
777+
778+
def test_tostring_default_namespace_original_no_namespace(self):
779+
elem = ET.XML('<body><tag/></body>')
780+
EXPECTED_MSG = '^cannot use non-qualified names with default_namespace option$'
781+
with self.assertRaisesRegex(ValueError, EXPECTED_MSG):
782+
ET.tostring(elem, encoding='unicode', default_namespace='foobar')
783+
784+
def test_tostring_no_xml_declaration(self):
785+
elem = ET.XML('<body><tag/></body>')
786+
self.assertEqual(
787+
ET.tostring(elem, encoding='unicode'),
788+
'<body><tag /></body>'
789+
)
790+
791+
def test_tostring_xml_declaration(self):
792+
elem = ET.XML('<body><tag/></body>')
793+
self.assertEqual(
794+
ET.tostring(elem, encoding='utf8', xml_declaration=True),
795+
b"<?xml version='1.0' encoding='utf8'?>\n<body><tag /></body>"
796+
)
797+
798+
def test_tostring_xml_declaration_unicode_encoding(self):
799+
elem = ET.XML('<body><tag/></body>')
800+
preferredencoding = locale.getpreferredencoding()
801+
self.assertEqual(
802+
f"<?xml version='1.0' encoding='{preferredencoding}'?>\n<body><tag /></body>",
803+
ET.tostring(elem, encoding='unicode', xml_declaration=True)
804+
)
805+
806+
def test_tostring_xml_declaration_cases(self):
807+
elem = ET.XML('<body><tag>ø</tag></body>')
808+
preferredencoding = locale.getpreferredencoding()
809+
TESTCASES = [
810+
# (expected_retval, encoding, xml_declaration)
811+
# ... xml_declaration = None
812+
(b'<body><tag>&#248;</tag></body>', None, None),
813+
(b'<body><tag>\xc3\xb8</tag></body>', 'UTF-8', None),
814+
(b'<body><tag>&#248;</tag></body>', 'US-ASCII', None),
815+
(b"<?xml version='1.0' encoding='ISO-8859-1'?>\n"
816+
b"<body><tag>\xf8</tag></body>", 'ISO-8859-1', None),
817+
('<body><tag>ø</tag></body>', 'unicode', None),
818+
819+
# ... xml_declaration = False
820+
(b"<body><tag>&#248;</tag></body>", None, False),
821+
(b"<body><tag>\xc3\xb8</tag></body>", 'UTF-8', False),
822+
(b"<body><tag>&#248;</tag></body>", 'US-ASCII', False),
823+
(b"<body><tag>\xf8</tag></body>", 'ISO-8859-1', False),
824+
("<body><tag>ø</tag></body>", 'unicode', False),
825+
826+
# ... xml_declaration = True
827+
(b"<?xml version='1.0' encoding='us-ascii'?>\n"
828+
b"<body><tag>&#248;</tag></body>", None, True),
829+
(b"<?xml version='1.0' encoding='UTF-8'?>\n"
830+
b"<body><tag>\xc3\xb8</tag></body>", 'UTF-8', True),
831+
(b"<?xml version='1.0' encoding='US-ASCII'?>\n"
832+
b"<body><tag>&#248;</tag></body>", 'US-ASCII', True),
833+
(b"<?xml version='1.0' encoding='ISO-8859-1'?>\n"
834+
b"<body><tag>\xf8</tag></body>", 'ISO-8859-1', True),
835+
(f"<?xml version='1.0' encoding='{preferredencoding}'?>\n"
836+
"<body><tag>ø</tag></body>", 'unicode', True),
837+
838+
]
839+
for expected_retval, encoding, xml_declaration in TESTCASES:
840+
with self.subTest(f'encoding={encoding} '
841+
f'xml_declaration={xml_declaration}'):
842+
self.assertEqual(
843+
ET.tostring(
844+
elem,
845+
encoding=encoding,
846+
xml_declaration=xml_declaration
847+
),
848+
expected_retval
849+
)
850+
851+
def test_tostringlist_default_namespace(self):
852+
elem = ET.XML('<body xmlns="http://effbot.org/ns"><tag/></body>')
853+
self.assertEqual(
854+
''.join(ET.tostringlist(elem, encoding='unicode')),
855+
'<ns0:body xmlns:ns0="http://effbot.org/ns"><ns0:tag /></ns0:body>'
856+
)
857+
self.assertEqual(
858+
''.join(ET.tostringlist(elem, encoding='unicode', default_namespace='http://effbot.org/ns')),
859+
'<body xmlns="http://effbot.org/ns"><tag /></body>'
860+
)
861+
862+
def test_tostringlist_xml_declaration(self):
863+
elem = ET.XML('<body><tag/></body>')
864+
self.assertEqual(
865+
''.join(ET.tostringlist(elem, encoding='unicode')),
866+
'<body><tag /></body>'
867+
)
868+
self.assertEqual(
869+
b''.join(ET.tostringlist(elem, xml_declaration=True)),
870+
b"<?xml version='1.0' encoding='us-ascii'?>\n<body><tag /></body>"
871+
)
872+
873+
preferredencoding = locale.getpreferredencoding()
874+
stringlist = ET.tostringlist(elem, encoding='unicode', xml_declaration=True)
875+
self.assertEqual(
876+
''.join(stringlist),
877+
f"<?xml version='1.0' encoding='{preferredencoding}'?>\n<body><tag /></body>"
878+
)
879+
self.assertRegex(stringlist[0], r"^<\?xml version='1.0' encoding='.+'?>")
880+
self.assertEqual(['<body', '>', '<tag', ' />', '</body>'], stringlist[1:])
881+
759882
def test_encoding(self):
760883
def check(encoding, body=''):
761884
xml = ("<?xml version='1.0' encoding='%s'?><xml>%s</xml>" %

Lib/xml/etree/ElementTree.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,6 +1113,7 @@ def _escape_attrib_html(text):
11131113
# --------------------------------------------------------------------
11141114

11151115
def tostring(element, encoding=None, method=None, *,
1116+
xml_declaration=None, default_namespace=None,
11161117
short_empty_elements=True):
11171118
"""Generate string representation of XML element.
11181119
@@ -1121,13 +1122,17 @@ def tostring(element, encoding=None, method=None, *,
11211122
11221123
*element* is an Element instance, *encoding* is an optional output
11231124
encoding defaulting to US-ASCII, *method* is an optional output which can
1124-
be one of "xml" (default), "html", "text" or "c14n".
1125+
be one of "xml" (default), "html", "text" or "c14n", *default_namespace*
1126+
sets the default XML namespace (for "xmlns").
11251127
11261128
Returns an (optionally) encoded string containing the XML data.
11271129
11281130
"""
11291131
stream = io.StringIO() if encoding == 'unicode' else io.BytesIO()
1130-
ElementTree(element).write(stream, encoding, method=method,
1132+
ElementTree(element).write(stream, encoding,
1133+
xml_declaration=xml_declaration,
1134+
default_namespace=default_namespace,
1135+
method=method,
11311136
short_empty_elements=short_empty_elements)
11321137
return stream.getvalue()
11331138

@@ -1149,10 +1154,14 @@ def tell(self):
11491154
return len(self.lst)
11501155

11511156
def tostringlist(element, encoding=None, method=None, *,
1157+
xml_declaration=None, default_namespace=None,
11521158
short_empty_elements=True):
11531159
lst = []
11541160
stream = _ListDataStream(lst)
1155-
ElementTree(element).write(stream, encoding, method=method,
1161+
ElementTree(element).write(stream, encoding,
1162+
xml_declaration=xml_declaration,
1163+
default_namespace=default_namespace,
1164+
method=method,
11561165
short_empty_elements=short_empty_elements)
11571166
return lst
11581167

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Added support for keyword arguments `default_namespace` and `xml_declaration` in functions
2+
ElementTree.tostring() and ElementTree.tostringlist().

0 commit comments

Comments
 (0)