Close #14377: Add a new parameter to ElementTree.write and some module-level

serialization functions - short_empty_elements. It controls how elements
without contents are emitted.

Patch by Serhiy Storchaka. Feature initially proposed by Ariel Poliak.
This commit is contained in:
Eli Bendersky 2013-01-13 06:04:43 -08:00
parent a50ff1887d
commit a9a2ef5550
4 changed files with 56 additions and 14 deletions

View File

@ -428,29 +428,39 @@ Functions
arguments. Returns an element instance. arguments. Returns an element instance.
.. function:: tostring(element, encoding="us-ascii", method="xml") .. function:: tostring(element, encoding="us-ascii", method="xml", *, \
short_empty_elements=True)
Generates a string representation of an XML element, including all Generates a string representation of an XML element, including all
subelements. *element* is an :class:`Element` instance. *encoding* [1]_ is subelements. *element* is an :class:`Element` instance. *encoding* [1]_ is
the output encoding (default is US-ASCII). Use ``encoding="unicode"`` to the output encoding (default is US-ASCII). Use ``encoding="unicode"`` to
generate a Unicode string (otherwise, a bytestring is generated). *method* generate a Unicode string (otherwise, a bytestring is generated). *method*
is either ``"xml"``, ``"html"`` or ``"text"`` (default is ``"xml"``). 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. Returns an (optionally) encoded string containing the XML data.
.. versionadded:: 3.4
The *short_empty_elements* parameter.
.. function:: tostringlist(element, encoding="us-ascii", method="xml")
.. function:: tostringlist(element, encoding="us-ascii", method="xml", *, \
short_empty_elements=True)
Generates a string representation of an XML element, including all Generates a string representation of an XML element, including all
subelements. *element* is an :class:`Element` instance. *encoding* [1]_ is subelements. *element* is an :class:`Element` instance. *encoding* [1]_ is
the output encoding (default is US-ASCII). Use ``encoding="unicode"`` to the output encoding (default is US-ASCII). Use ``encoding="unicode"`` to
generate a Unicode string (otherwise, a bytestring is generated). *method* generate a Unicode string (otherwise, a bytestring is generated). *method*
is either ``"xml"``, ``"html"`` or ``"text"`` (default is ``"xml"``). 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. Returns a list of (optionally) encoded strings containing the XML data.
It does not guarantee any specific sequence, except that It does not guarantee any specific sequence, except that
``"".join(tostringlist(element)) == tostring(element)``. ``"".join(tostringlist(element)) == tostring(element)``.
.. versionadded:: 3.2 .. versionadded:: 3.2
.. versionadded:: 3.4
The *short_empty_elements* parameter.
.. function:: XML(text, parser=None) .. function:: XML(text, parser=None)
@ -742,7 +752,7 @@ ElementTree Objects
.. method:: write(file, encoding="us-ascii", xml_declaration=None, \ .. method:: write(file, encoding="us-ascii", xml_declaration=None, \
method="xml") method="xml", *, short_empty_elements=True)
Writes the element tree to a file, as XML. *file* is a file name, or a 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 :term:`file object` opened for writing. *encoding* [1]_ is the output
@ -752,6 +762,10 @@ ElementTree Objects
for only if not US-ASCII or UTF-8 or Unicode (default is ``None``). for only if not US-ASCII or UTF-8 or Unicode (default is ``None``).
*method* is either ``"xml"``, ``"html"`` or ``"text"`` (default is *method* is either ``"xml"``, ``"html"`` or ``"text"`` (default is
``"xml"``). ``"xml"``).
The keyword-only *short_empty_elements* parameter controls the formatting
of elements that contain no content. If *True* (the default), they are
emitted as a single self-closed tag, otherwise they are emitted as a pair
of start/end tags.
The output is either a string (:class:`str`) or binary (:class:`bytes`). The output is either a string (:class:`str`) or binary (:class:`bytes`).
This is controlled by the *encoding* argument. If *encoding* is This is controlled by the *encoding* argument. If *encoding* is
@ -760,6 +774,9 @@ ElementTree Objects
:term:`file object`; make sure you do not try to write a string to a :term:`file object`; make sure you do not try to write a string to a
binary stream and vice versa. binary stream and vice versa.
.. versionadded:: 3.4
The *short_empty_elements* parameter.
This is the XML file that is going to be manipulated:: This is the XML file that is going to be manipulated::

View File

@ -2380,6 +2380,18 @@ class IOTest(unittest.TestCase):
ET.tostring(root, 'utf-16'), ET.tostring(root, 'utf-16'),
b''.join(ET.tostringlist(root, 'utf-16'))) b''.join(ET.tostringlist(root, 'utf-16')))
def test_short_empty_elements(self):
root = ET.fromstring('<tag>a<x />b<y></y>c</tag>')
self.assertEqual(
ET.tostring(root, 'unicode'),
'<tag>a<x />b<y />c</tag>')
self.assertEqual(
ET.tostring(root, 'unicode', short_empty_elements=True),
'<tag>a<x />b<y />c</tag>')
self.assertEqual(
ET.tostring(root, 'unicode', short_empty_elements=False),
'<tag>a<x></x>b<y></y>c</tag>')
class ParseErrorTest(unittest.TestCase): class ParseErrorTest(unittest.TestCase):
def test_subclass(self): def test_subclass(self):

View File

@ -797,7 +797,8 @@ class ElementTree:
encoding=None, encoding=None,
xml_declaration=None, xml_declaration=None,
default_namespace=None, default_namespace=None,
method=None): method=None, *,
short_empty_elements=True):
if not method: if not method:
method = "xml" method = "xml"
elif method not in _serialize: elif method not in _serialize:
@ -825,7 +826,8 @@ class ElementTree:
else: else:
qnames, namespaces = _namespaces(self._root, default_namespace) qnames, namespaces = _namespaces(self._root, default_namespace)
serialize = _serialize[method] serialize = _serialize[method]
serialize(write, self._root, qnames, namespaces) serialize(write, self._root, qnames, namespaces,
short_empty_elements=short_empty_elements)
def write_c14n(self, file): def write_c14n(self, file):
# lxml.etree compatibility. use output method instead # lxml.etree compatibility. use output method instead
@ -947,7 +949,8 @@ def _namespaces(elem, default_namespace=None):
add_qname(text.text) add_qname(text.text)
return qnames, namespaces return qnames, namespaces
def _serialize_xml(write, elem, qnames, namespaces): def _serialize_xml(write, elem, qnames, namespaces,
short_empty_elements, **kwargs):
tag = elem.tag tag = elem.tag
text = elem.text text = elem.text
if tag is Comment: if tag is Comment:
@ -960,7 +963,8 @@ def _serialize_xml(write, elem, qnames, namespaces):
if text: if text:
write(_escape_cdata(text)) write(_escape_cdata(text))
for e in elem: for e in elem:
_serialize_xml(write, e, qnames, None) _serialize_xml(write, e, qnames, None,
short_empty_elements=short_empty_elements)
else: else:
write("<" + tag) write("<" + tag)
items = list(elem.items()) items = list(elem.items())
@ -982,12 +986,13 @@ def _serialize_xml(write, elem, qnames, namespaces):
else: else:
v = _escape_attrib(v) v = _escape_attrib(v)
write(" %s=\"%s\"" % (qnames[k], v)) write(" %s=\"%s\"" % (qnames[k], v))
if text or len(elem): if text or len(elem) or not short_empty_elements:
write(">") write(">")
if text: if text:
write(_escape_cdata(text)) write(_escape_cdata(text))
for e in elem: for e in elem:
_serialize_xml(write, e, qnames, None) _serialize_xml(write, e, qnames, None,
short_empty_elements=short_empty_elements)
write("</" + tag + ">") write("</" + tag + ">")
else: else:
write(" />") write(" />")
@ -1002,7 +1007,7 @@ try:
except NameError: except NameError:
pass pass
def _serialize_html(write, elem, qnames, namespaces): def _serialize_html(write, elem, qnames, namespaces, **kwargs):
tag = elem.tag tag = elem.tag
text = elem.text text = elem.text
if tag is Comment: if tag is Comment:
@ -1166,9 +1171,11 @@ def _escape_attrib_html(text):
# @return An (optionally) encoded string containing the XML data. # @return An (optionally) encoded string containing the XML data.
# @defreturn string # @defreturn string
def tostring(element, encoding=None, method=None): def tostring(element, encoding=None, method=None, *,
short_empty_elements=True):
stream = io.StringIO() if encoding == 'unicode' else io.BytesIO() stream = io.StringIO() if encoding == 'unicode' else io.BytesIO()
ElementTree(element).write(stream, encoding, method=method) ElementTree(element).write(stream, encoding, method=method,
short_empty_elements=short_empty_elements)
return stream.getvalue() return stream.getvalue()
## ##
@ -1202,10 +1209,12 @@ class _ListDataStream(io.BufferedIOBase):
def tell(self): def tell(self):
return len(self.lst) return len(self.lst)
def tostringlist(element, encoding=None, method=None): def tostringlist(element, encoding=None, method=None, *,
short_empty_elements=True):
lst = [] lst = []
stream = _ListDataStream(lst) stream = _ListDataStream(lst)
ElementTree(element).write(stream, encoding, method=method) ElementTree(element).write(stream, encoding, method=method,
short_empty_elements=short_empty_elements)
return lst return lst
## ##

View File

@ -559,6 +559,10 @@ Library
- Issue #16123: IDLE - deprecate running without a subprocess. - Issue #16123: IDLE - deprecate running without a subprocess.
Patch by Roger Serwy. Patch by Roger Serwy.
- Issue #14377: ElementTree.write and some of the module-level functions have
a new parameter - *short_empty_elements*. It controls how elements with no
contents are emitted.
- Issue #16089: Allow ElementTree.TreeBuilder to work again with a non-Element - Issue #16089: Allow ElementTree.TreeBuilder to work again with a non-Element
element_factory (fixes a regression in SimpleTAL). element_factory (fixes a regression in SimpleTAL).