mirror of
https://github.com/python/cpython.git
synced 2024-11-27 20:04:41 +08:00
#1343: Add short_empty_elements option to XMLGenerator.
Patch and tests by Neil Muller.
This commit is contained in:
parent
961aaf5cfa
commit
a90032a3fb
@ -50,13 +50,19 @@ or as base classes.
|
|||||||
using the reference concrete syntax.
|
using the reference concrete syntax.
|
||||||
|
|
||||||
|
|
||||||
.. class:: XMLGenerator(out=None, encoding='iso-8859-1')
|
.. class:: XMLGenerator(out=None, encoding='iso-8859-1', short_empty_elements=False)
|
||||||
|
|
||||||
This class implements the :class:`ContentHandler` interface by writing SAX
|
This class implements the :class:`ContentHandler` interface by writing SAX
|
||||||
events back into an XML document. In other words, using an :class:`XMLGenerator`
|
events back into an XML document. In other words, using an :class:`XMLGenerator`
|
||||||
as the content handler will reproduce the original document being parsed. *out*
|
as the content handler will reproduce the original document being parsed. *out*
|
||||||
should be a file-like object which will default to *sys.stdout*. *encoding* is
|
should be a file-like object which will default to *sys.stdout*. *encoding* is
|
||||||
the encoding of the output stream which defaults to ``'iso-8859-1'``.
|
the encoding of the output stream which defaults to ``'iso-8859-1'``.
|
||||||
|
*short_empty_elements* controls the formatting of elements that contain no
|
||||||
|
content: if *False* (the default) they are emitted as a pair of start/end
|
||||||
|
tags, if set to *True* they are emitted as a single self-closed tag.
|
||||||
|
|
||||||
|
.. versionadded:: 3.2
|
||||||
|
short_empty_elements
|
||||||
|
|
||||||
|
|
||||||
.. class:: XMLFilterBase(base)
|
.. class:: XMLFilterBase(base)
|
||||||
|
@ -170,6 +170,16 @@ class XmlgenTest(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEquals(result.getvalue(), start + "<doc></doc>")
|
self.assertEquals(result.getvalue(), start + "<doc></doc>")
|
||||||
|
|
||||||
|
def test_xmlgen_basic_empty(self):
|
||||||
|
result = StringIO()
|
||||||
|
gen = XMLGenerator(result, short_empty_elements=True)
|
||||||
|
gen.startDocument()
|
||||||
|
gen.startElement("doc", {})
|
||||||
|
gen.endElement("doc")
|
||||||
|
gen.endDocument()
|
||||||
|
|
||||||
|
self.assertEquals(result.getvalue(), start + "<doc/>")
|
||||||
|
|
||||||
def test_xmlgen_content(self):
|
def test_xmlgen_content(self):
|
||||||
result = StringIO()
|
result = StringIO()
|
||||||
gen = XMLGenerator(result)
|
gen = XMLGenerator(result)
|
||||||
@ -182,6 +192,18 @@ class XmlgenTest(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEquals(result.getvalue(), start + "<doc>huhei</doc>")
|
self.assertEquals(result.getvalue(), start + "<doc>huhei</doc>")
|
||||||
|
|
||||||
|
def test_xmlgen_content_empty(self):
|
||||||
|
result = StringIO()
|
||||||
|
gen = XMLGenerator(result, short_empty_elements=True)
|
||||||
|
|
||||||
|
gen.startDocument()
|
||||||
|
gen.startElement("doc", {})
|
||||||
|
gen.characters("huhei")
|
||||||
|
gen.endElement("doc")
|
||||||
|
gen.endDocument()
|
||||||
|
|
||||||
|
self.assertEquals(result.getvalue(), start + "<doc>huhei</doc>")
|
||||||
|
|
||||||
def test_xmlgen_pi(self):
|
def test_xmlgen_pi(self):
|
||||||
result = StringIO()
|
result = StringIO()
|
||||||
gen = XMLGenerator(result)
|
gen = XMLGenerator(result)
|
||||||
@ -239,6 +261,18 @@ class XmlgenTest(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEquals(result.getvalue(), start + "<doc> </doc>")
|
self.assertEquals(result.getvalue(), start + "<doc> </doc>")
|
||||||
|
|
||||||
|
def test_xmlgen_ignorable_empty(self):
|
||||||
|
result = StringIO()
|
||||||
|
gen = XMLGenerator(result, short_empty_elements=True)
|
||||||
|
|
||||||
|
gen.startDocument()
|
||||||
|
gen.startElement("doc", {})
|
||||||
|
gen.ignorableWhitespace(" ")
|
||||||
|
gen.endElement("doc")
|
||||||
|
gen.endDocument()
|
||||||
|
|
||||||
|
self.assertEquals(result.getvalue(), start + "<doc> </doc>")
|
||||||
|
|
||||||
def test_xmlgen_ns(self):
|
def test_xmlgen_ns(self):
|
||||||
result = StringIO()
|
result = StringIO()
|
||||||
gen = XMLGenerator(result)
|
gen = XMLGenerator(result)
|
||||||
@ -257,6 +291,24 @@ class XmlgenTest(unittest.TestCase):
|
|||||||
('<ns1:doc xmlns:ns1="%s"><udoc></udoc></ns1:doc>' %
|
('<ns1:doc xmlns:ns1="%s"><udoc></udoc></ns1:doc>' %
|
||||||
ns_uri))
|
ns_uri))
|
||||||
|
|
||||||
|
def test_xmlgen_ns_empty(self):
|
||||||
|
result = StringIO()
|
||||||
|
gen = XMLGenerator(result, short_empty_elements=True)
|
||||||
|
|
||||||
|
gen.startDocument()
|
||||||
|
gen.startPrefixMapping("ns1", ns_uri)
|
||||||
|
gen.startElementNS((ns_uri, "doc"), "ns1:doc", {})
|
||||||
|
# add an unqualified name
|
||||||
|
gen.startElementNS((None, "udoc"), None, {})
|
||||||
|
gen.endElementNS((None, "udoc"), None)
|
||||||
|
gen.endElementNS((ns_uri, "doc"), "ns1:doc")
|
||||||
|
gen.endPrefixMapping("ns1")
|
||||||
|
gen.endDocument()
|
||||||
|
|
||||||
|
self.assertEquals(result.getvalue(), start + \
|
||||||
|
('<ns1:doc xmlns:ns1="%s"><udoc/></ns1:doc>' %
|
||||||
|
ns_uri))
|
||||||
|
|
||||||
def test_1463026_1(self):
|
def test_1463026_1(self):
|
||||||
result = StringIO()
|
result = StringIO()
|
||||||
gen = XMLGenerator(result)
|
gen = XMLGenerator(result)
|
||||||
@ -268,6 +320,17 @@ class XmlgenTest(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEquals(result.getvalue(), start+'<a b="c"></a>')
|
self.assertEquals(result.getvalue(), start+'<a b="c"></a>')
|
||||||
|
|
||||||
|
def test_1463026_1_empty(self):
|
||||||
|
result = StringIO()
|
||||||
|
gen = XMLGenerator(result, short_empty_elements=True)
|
||||||
|
|
||||||
|
gen.startDocument()
|
||||||
|
gen.startElementNS((None, 'a'), 'a', {(None, 'b'):'c'})
|
||||||
|
gen.endElementNS((None, 'a'), 'a')
|
||||||
|
gen.endDocument()
|
||||||
|
|
||||||
|
self.assertEquals(result.getvalue(), start+'<a b="c"/>')
|
||||||
|
|
||||||
def test_1463026_2(self):
|
def test_1463026_2(self):
|
||||||
result = StringIO()
|
result = StringIO()
|
||||||
gen = XMLGenerator(result)
|
gen = XMLGenerator(result)
|
||||||
@ -281,6 +344,19 @@ class XmlgenTest(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEquals(result.getvalue(), start+'<a xmlns="qux"></a>')
|
self.assertEquals(result.getvalue(), start+'<a xmlns="qux"></a>')
|
||||||
|
|
||||||
|
def test_1463026_2_empty(self):
|
||||||
|
result = StringIO()
|
||||||
|
gen = XMLGenerator(result, short_empty_elements=True)
|
||||||
|
|
||||||
|
gen.startDocument()
|
||||||
|
gen.startPrefixMapping(None, 'qux')
|
||||||
|
gen.startElementNS(('qux', 'a'), 'a', {})
|
||||||
|
gen.endElementNS(('qux', 'a'), 'a')
|
||||||
|
gen.endPrefixMapping(None)
|
||||||
|
gen.endDocument()
|
||||||
|
|
||||||
|
self.assertEquals(result.getvalue(), start+'<a xmlns="qux"/>')
|
||||||
|
|
||||||
def test_1463026_3(self):
|
def test_1463026_3(self):
|
||||||
result = StringIO()
|
result = StringIO()
|
||||||
gen = XMLGenerator(result)
|
gen = XMLGenerator(result)
|
||||||
@ -295,6 +371,20 @@ class XmlgenTest(unittest.TestCase):
|
|||||||
self.assertEquals(result.getvalue(),
|
self.assertEquals(result.getvalue(),
|
||||||
start+'<my:a xmlns:my="qux" b="c"></my:a>')
|
start+'<my:a xmlns:my="qux" b="c"></my:a>')
|
||||||
|
|
||||||
|
def test_1463026_3_empty(self):
|
||||||
|
result = StringIO()
|
||||||
|
gen = XMLGenerator(result, short_empty_elements=True)
|
||||||
|
|
||||||
|
gen.startDocument()
|
||||||
|
gen.startPrefixMapping('my', 'qux')
|
||||||
|
gen.startElementNS(('qux', 'a'), 'a', {(None, 'b'):'c'})
|
||||||
|
gen.endElementNS(('qux', 'a'), 'a')
|
||||||
|
gen.endPrefixMapping('my')
|
||||||
|
gen.endDocument()
|
||||||
|
|
||||||
|
self.assertEquals(result.getvalue(),
|
||||||
|
start+'<my:a xmlns:my="qux" b="c"/>')
|
||||||
|
|
||||||
|
|
||||||
class XMLFilterBaseTest(unittest.TestCase):
|
class XMLFilterBaseTest(unittest.TestCase):
|
||||||
def test_filter_basic(self):
|
def test_filter_basic(self):
|
||||||
|
@ -78,7 +78,7 @@ def quoteattr(data, entities={}):
|
|||||||
|
|
||||||
class XMLGenerator(handler.ContentHandler):
|
class XMLGenerator(handler.ContentHandler):
|
||||||
|
|
||||||
def __init__(self, out=None, encoding="iso-8859-1"):
|
def __init__(self, out=None, encoding="iso-8859-1", short_empty_elements=False):
|
||||||
if out is None:
|
if out is None:
|
||||||
import sys
|
import sys
|
||||||
out = sys.stdout
|
out = sys.stdout
|
||||||
@ -88,6 +88,8 @@ class XMLGenerator(handler.ContentHandler):
|
|||||||
self._current_context = self._ns_contexts[-1]
|
self._current_context = self._ns_contexts[-1]
|
||||||
self._undeclared_ns_maps = []
|
self._undeclared_ns_maps = []
|
||||||
self._encoding = encoding
|
self._encoding = encoding
|
||||||
|
self._short_empty_elements = short_empty_elements
|
||||||
|
self._pending_start_element = False
|
||||||
|
|
||||||
def _write(self, text):
|
def _write(self, text):
|
||||||
if isinstance(text, str):
|
if isinstance(text, str):
|
||||||
@ -106,6 +108,11 @@ class XMLGenerator(handler.ContentHandler):
|
|||||||
# Return the unqualified name
|
# Return the unqualified name
|
||||||
return name[1]
|
return name[1]
|
||||||
|
|
||||||
|
def _finish_pending_start_element(self,endElement=False):
|
||||||
|
if self._pending_start_element:
|
||||||
|
self._write('>')
|
||||||
|
self._pending_start_element = False
|
||||||
|
|
||||||
# ContentHandler methods
|
# ContentHandler methods
|
||||||
|
|
||||||
def startDocument(self):
|
def startDocument(self):
|
||||||
@ -122,15 +129,24 @@ class XMLGenerator(handler.ContentHandler):
|
|||||||
del self._ns_contexts[-1]
|
del self._ns_contexts[-1]
|
||||||
|
|
||||||
def startElement(self, name, attrs):
|
def startElement(self, name, attrs):
|
||||||
|
self._finish_pending_start_element()
|
||||||
self._write('<' + name)
|
self._write('<' + name)
|
||||||
for (name, value) in attrs.items():
|
for (name, value) in attrs.items():
|
||||||
self._write(' %s=%s' % (name, quoteattr(value)))
|
self._write(' %s=%s' % (name, quoteattr(value)))
|
||||||
self._write('>')
|
if self._short_empty_elements:
|
||||||
|
self._pending_start_element = True
|
||||||
|
else:
|
||||||
|
self._write(">")
|
||||||
|
|
||||||
def endElement(self, name):
|
def endElement(self, name):
|
||||||
self._write('</%s>' % name)
|
if self._pending_start_element:
|
||||||
|
self._write('/>')
|
||||||
|
self._pending_start_element = False
|
||||||
|
else:
|
||||||
|
self._write('</%s>' % name)
|
||||||
|
|
||||||
def startElementNS(self, name, qname, attrs):
|
def startElementNS(self, name, qname, attrs):
|
||||||
|
self._finish_pending_start_element()
|
||||||
self._write('<' + self._qname(name))
|
self._write('<' + self._qname(name))
|
||||||
|
|
||||||
for prefix, uri in self._undeclared_ns_maps:
|
for prefix, uri in self._undeclared_ns_maps:
|
||||||
@ -142,18 +158,30 @@ class XMLGenerator(handler.ContentHandler):
|
|||||||
|
|
||||||
for (name, value) in attrs.items():
|
for (name, value) in attrs.items():
|
||||||
self._write(' %s=%s' % (self._qname(name), quoteattr(value)))
|
self._write(' %s=%s' % (self._qname(name), quoteattr(value)))
|
||||||
self._write('>')
|
if self._short_empty_elements:
|
||||||
|
self._pending_start_element = True
|
||||||
|
else:
|
||||||
|
self._write(">")
|
||||||
|
|
||||||
def endElementNS(self, name, qname):
|
def endElementNS(self, name, qname):
|
||||||
self._write('</%s>' % self._qname(name))
|
if self._pending_start_element:
|
||||||
|
self._write('/>')
|
||||||
|
self._pending_start_element = False
|
||||||
|
else:
|
||||||
|
self._write('</%s>' % self._qname(name))
|
||||||
|
|
||||||
def characters(self, content):
|
def characters(self, content):
|
||||||
self._write(escape(content))
|
if content:
|
||||||
|
self._finish_pending_start_element()
|
||||||
|
self._write(escape(content))
|
||||||
|
|
||||||
def ignorableWhitespace(self, content):
|
def ignorableWhitespace(self, content):
|
||||||
self._write(content)
|
if content:
|
||||||
|
self._finish_pending_start_element()
|
||||||
|
self._write(content)
|
||||||
|
|
||||||
def processingInstruction(self, target, data):
|
def processingInstruction(self, target, data):
|
||||||
|
self._finish_pending_start_element()
|
||||||
self._write('<?%s %s?>' % (target, data))
|
self._write('<?%s %s?>' % (target, data))
|
||||||
|
|
||||||
|
|
||||||
|
@ -34,6 +34,9 @@ Core and Builtins
|
|||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #1343: xml.sax.saxutils.XMLGenerator now has an option
|
||||||
|
short_empty_elements to direct it to use self-closing tags when appropriate.
|
||||||
|
|
||||||
- Issue #9807 (part 1): Expose the ABI flags in sys.abiflags. Add --abiflags
|
- Issue #9807 (part 1): Expose the ABI flags in sys.abiflags. Add --abiflags
|
||||||
switch to python-config for command line access.
|
switch to python-config for command line access.
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user