Skip to content
Open
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
43 changes: 33 additions & 10 deletions Doc/library/xml.etree.elementtree.rst
Original file line number Diff line number Diff line change
Expand Up @@ -739,9 +739,19 @@ Functions
*prefix* is a namespace prefix. *uri* is a namespace uri. Tags and
attributes in this namespace will be serialized with the given prefix, if at
all possible.
:exc:`ValueError` is raised if *prefix* is invalid or reserved
(``ns`` followed by digits is reserved for the serializer).

The registry is meant for well-known prefixes of the application.
To choose the prefixes for a particular serialization,
use the *namespaces* parameter of :func:`tostring`, :func:`tostringlist`
and :meth:`ElementTree.write` instead.

.. versionadded:: 3.2

.. versionchanged:: next
Invalid and reserved prefixes are now rejected.


.. function:: SubElement(parent, tag, /, attrib={}, **extra)

Expand All @@ -762,15 +772,17 @@ Functions

.. function:: tostring(element, encoding="us-ascii", method="xml", *, \
xml_declaration=None, default_namespace=None, \
short_empty_elements=True, standalone=None)
short_empty_elements=True, standalone=None, \
namespaces=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*, *short_empty_elements* and
*standalone* has the same meaning as in :meth:`ElementTree.write`.
*xml_declaration*, *default_namespace*, *short_empty_elements*,
*standalone* and *namespaces* have the same meaning as in
:meth:`ElementTree.write`.
Returns an (optionally) encoded string containing the XML data.

.. versionchanged:: 3.4
Expand All @@ -784,20 +796,22 @@ Functions
specified by the user.

.. versionchanged:: next
Added the *standalone* parameter.
Added the *standalone* and *namespaces* parameters.


.. function:: tostringlist(element, encoding="us-ascii", method="xml", *, \
xml_declaration=None, default_namespace=None, \
short_empty_elements=True, standalone=None)
short_empty_elements=True, standalone=None, \
namespaces=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*, *short_empty_elements* and
*standalone* has the same meaning as in :meth:`ElementTree.write`.
*xml_declaration*, *default_namespace*, *short_empty_elements*,
*standalone* and *namespaces* have 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)``.
Expand All @@ -815,7 +829,7 @@ Functions
specified by the user.

.. versionchanged:: next
Added the *standalone* parameter.
Added the *standalone* and *namespaces* parameters.


.. function:: XML(text, parser=None)
Expand Down Expand Up @@ -1253,7 +1267,8 @@ ElementTree Objects

.. method:: write(file, encoding="us-ascii", xml_declaration=None, \
default_namespace=None, method="xml", *, \
short_empty_elements=True, standalone=None)
short_empty_elements=True, standalone=None, \
namespaces=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
Expand All @@ -1276,6 +1291,14 @@ ElementTree Objects
An XML declaration is written if *standalone* is not ``None``;
combining it with ``xml_declaration=False`` raises a :exc:`ValueError`.

The keyword-only *namespaces* parameter is a mapping from namespace
prefixes to URIs, which is used to choose the prefixes for this
serialization instead of the prefixes registered with
:func:`register_namespace`.
Only the namespaces used in the tree are declared.
The empty prefix sets the default namespace, like *default_namespace*.
The prefixes are validated as in :func:`register_namespace`.

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
Expand All @@ -1291,7 +1314,7 @@ ElementTree Objects
by the user.

.. versionchanged:: next
Added the *standalone* parameter.
Added the *standalone* and *namespaces* parameters.


This is the XML file that is going to be manipulated::
Expand Down
7 changes: 7 additions & 0 deletions Doc/whatsnew/3.16.rst
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,13 @@ xml
now work for :class:`!DocumentFragment` nodes in :mod:`xml.dom.minidom`.
(Contributed by Serhiy Storchaka in :gh:`54092`.)

* Add the *namespaces* parameter to :func:`~xml.etree.ElementTree.tostring`,
:func:`~xml.etree.ElementTree.tostringlist` and
:meth:`ElementTree.write <xml.etree.ElementTree.ElementTree.write>`,
a mapping from namespace prefixes to URIs which chooses the prefixes
for this serialization instead of the global registry.
(Contributed by Serhiy Storchaka in :gh:`57587`.)

* :class:`~xml.etree.ElementTree.XMLPullParser` and
:func:`~xml.etree.ElementTree.iterparse` now support the *target* parameter.
The reported object is the value returned by the corresponding method of
Expand Down
108 changes: 108 additions & 0 deletions Lib/test/test_xml_etree.py
Original file line number Diff line number Diff line change
Expand Up @@ -1082,6 +1082,84 @@ def test_tostring_default_namespace_attributes_html(self):
'<body xmlns="http://effbot.org/ns" attr="value"></body>'
)

def test_tostring_namespaces(self):
# gh-57587: the prefixes for a particular serialization
house = 'http://localhost/house'
geo = 'http://localhost/geo'
elem = ET.XML('<house:iq xmlns:house="%s"/>' % house)
self.assertEqual(serialize(elem),
'<ns0:iq xmlns:ns0="http://localhost/house" />')
self.assertEqual(serialize(elem, namespaces={'house': house}),
'<house:iq xmlns:house="http://localhost/house" />')
self.assertEqual(serialize(elem, namespaces={'home': house}),
'<home:iq xmlns:home="http://localhost/house" />')
# the empty prefix sets the default namespace
self.assertEqual(serialize(elem, namespaces={'': house}),
'<iq xmlns="http://localhost/house" />')
self.assertEqual(serialize(elem, namespaces={'': house},
default_namespace=house),
'<iq xmlns="http://localhost/house" />')
with self.assertRaisesRegex(ValueError, 'conflicting default'):
serialize(elem, namespaces={'': house}, default_namespace=geo)
# only the namespaces used in the tree are declared
self.assertEqual(serialize(elem, namespaces={'house': house, 'geo': geo}),
'<house:iq xmlns:house="http://localhost/house" />')

def test_tostring_namespaces_registry(self):
house = 'http://localhost/house'
geo = 'http://localhost/geo'
elem = ET.XML('<doc><geo:town xmlns:geo="%s">'
'<house:iq xmlns:house="%s"/></geo:town></doc>'
% (geo, house))
ET.register_namespace('geo', geo)
self.addCleanup(ET._namespace_map.pop, geo, None)
self.assertEqual(serialize(elem),
'<doc xmlns:geo="http://localhost/geo" '
'xmlns:ns1="http://localhost/house">'
'<geo:town><ns1:iq /></geo:town></doc>')
# the mapping takes precedence over the registry
self.assertEqual(serialize(elem, namespaces={'g': geo}),
'<doc xmlns:g="http://localhost/geo" '
'xmlns:ns1="http://localhost/house">'
'<g:town><ns1:iq /></g:town></doc>')
# a registered prefix is not used if the mapping reserves it
# for another namespace
self.assertEqual(serialize(elem, namespaces={'geo': house}),
'<doc xmlns:geo="http://localhost/house" '
'xmlns:ns0="http://localhost/geo">'
'<ns0:town><geo:iq /></ns0:town></doc>')

def test_tostring_namespaces_attributes(self):
house = 'http://localhost/house'
geo = 'http://localhost/geo'
elem = ET.Element('{%s}a' % house, {'{%s}k' % geo: 'v', 'x': '1'})
self.assertEqual(serialize(elem, namespaces={'': house, 'g': geo}),
'<a xmlns="http://localhost/house" '
'xmlns:g="http://localhost/geo" g:k="v" x="1" />')
# an attribute cannot use the default namespace
elem = ET.Element('{%s}a' % house, {'{%s}k' % house: 'v'})
self.assertEqual(serialize(elem, namespaces={'': house, 'h': house}),
'<a xmlns="http://localhost/house" '
'xmlns:h="http://localhost/house" h:k="v" />')

def test_tostring_namespaces_invalid(self):
elem = ET.XML('<a/>')
for namespaces in [{'ns0': 'uri'}, {'ns12': 'uri'}, {'xml': 'uri'},
{'xmlns': 'uri'}, {'a:b': 'uri'}, {'1': 'uri'},
{'a b': 'uri'}]:
with self.subTest(namespaces=namespaces):
self.assertRaises(ValueError, serialize, elem,
namespaces=namespaces)
for namespaces in [{1: 'uri'}, {'a': 1}, {b'a': 'uri'}]:
with self.subTest(namespaces=namespaces):
self.assertRaises(TypeError, serialize, elem,
namespaces=namespaces)
# the xml prefix can only be mapped to its namespace
self.assertEqual(
serialize(elem, namespaces={
'xml': 'http://www.w3.org/XML/1998/namespace'}),
'<a />')

def test_tostring_standalone(self):
elem = ET.XML('<body><tag/></body>')
self.assertEqual(
Expand Down Expand Up @@ -2848,6 +2926,36 @@ def test_bug_200709_register_namespace(self):
self.assertEqual(ET.tostring(e),
b'<dc:title xmlns:dc="http://purl.org/dc/elements/1.1/" />')

def test_register_namespace_invalid(self):
# gh-157518
xml_ns = 'http://www.w3.org/XML/1998/namespace'
xmlns_ns = 'http://www.w3.org/2000/xmlns/'
nsmap = ET.register_namespace._namespace_map
saved = dict(nsmap)
for prefix, uri in [
('ns0', 'u'), ('ns12', 'u'),
('xml', 'u'), ('foo', xml_ns),
('xmlns', 'u'), ('foo', xmlns_ns),
('a:b', 'u'), ('1', 'u'), ('a b', 'u'), ('a\xa0b', 'u'),
]:
with self.subTest(prefix=prefix, uri=uri):
with self.assertRaises(ValueError):
ET.register_namespace(prefix, uri)
# the registry is not changed
self.assertEqual(nsmap, saved)
for prefix, uri in [(1, 'u'), (b'a', 'u'), ('a', 1), ('a', None)]:
with self.subTest(prefix=prefix, uri=uri):
with self.assertRaises(TypeError):
ET.register_namespace(prefix, uri)
# the xml prefix can be registered for its namespace
ET.register_namespace('xml', xml_ns)
self.assertEqual(ET.register_namespace._namespace_map[xml_ns], 'xml')
# non-ASCII names are valid
self.addCleanup(ET.register_namespace._namespace_map.pop, 'u', None)
ET.register_namespace('\xe9', 'u')
self.assertEqual(ET.tostring(ET.Element('{u}a'), encoding='unicode'),
'<\xe9:a xmlns:\xe9="u" />')

def test_bug_200709_element_comment(self):
# Not sure if this can be fixed, really (since the serializer needs
# ET.Comment, not cET.comment).
Expand Down
Loading
Loading