]> granicus.if.org Git - python/commitdiff
#1343: Add short_empty_elements option to XMLGenerator.
authorR. David Murray <rdmurray@bitdance.com>
Sun, 17 Oct 2010 22:46:45 +0000 (22:46 +0000)
committerR. David Murray <rdmurray@bitdance.com>
Sun, 17 Oct 2010 22:46:45 +0000 (22:46 +0000)
Patch and tests by Neil Muller.

Doc/library/xml.sax.utils.rst
Lib/test/test_sax.py
Lib/xml/sax/saxutils.py
Misc/NEWS

index 95099f67092b139b5480dbf0f494c9d5440672c6..ff36fd89e89988b75811f6c4f988421e051ebf9a 100644 (file)
@@ -50,13 +50,19 @@ or as base classes.
    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
    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*
    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'``.
+   *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)
index 911e7634c24133e7c1ee4395e58ca99646c2ab66..143ddf2940d828207c6956ae40b91f2a7bcb1db3 100644 (file)
@@ -170,6 +170,16 @@ class XmlgenTest(unittest.TestCase):
 
         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):
         result = StringIO()
         gen = XMLGenerator(result)
@@ -182,6 +192,18 @@ class XmlgenTest(unittest.TestCase):
 
         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):
         result = StringIO()
         gen = XMLGenerator(result)
@@ -239,6 +261,18 @@ class XmlgenTest(unittest.TestCase):
 
         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):
         result = StringIO()
         gen = XMLGenerator(result)
@@ -257,6 +291,24 @@ class XmlgenTest(unittest.TestCase):
            ('<ns1:doc xmlns:ns1="%s"><udoc></udoc></ns1:doc>' %
                                          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):
         result = StringIO()
         gen = XMLGenerator(result)
@@ -268,6 +320,17 @@ class XmlgenTest(unittest.TestCase):
 
         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):
         result = StringIO()
         gen = XMLGenerator(result)
@@ -281,6 +344,19 @@ class XmlgenTest(unittest.TestCase):
 
         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):
         result = StringIO()
         gen = XMLGenerator(result)
@@ -295,6 +371,20 @@ class XmlgenTest(unittest.TestCase):
         self.assertEquals(result.getvalue(),
             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):
     def test_filter_basic(self):
index e8450158cf2d3b0ee141f6ac8b759285624b4c59..46946fcf72e8775d27a15b067d972ac817bbeb55 100644 (file)
@@ -78,7 +78,7 @@ def quoteattr(data, entities={}):
 
 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:
             import sys
             out = sys.stdout
@@ -88,6 +88,8 @@ class XMLGenerator(handler.ContentHandler):
         self._current_context = self._ns_contexts[-1]
         self._undeclared_ns_maps = []
         self._encoding = encoding
+        self._short_empty_elements = short_empty_elements
+        self._pending_start_element = False
 
     def _write(self, text):
         if isinstance(text, str):
@@ -106,6 +108,11 @@ class XMLGenerator(handler.ContentHandler):
         # Return the unqualified name
         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
 
     def startDocument(self):
@@ -122,15 +129,24 @@ class XMLGenerator(handler.ContentHandler):
         del self._ns_contexts[-1]
 
     def startElement(self, name, attrs):
+        self._finish_pending_start_element()
         self._write('<' + name)
         for (name, value) in attrs.items():
             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):
-        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):
+        self._finish_pending_start_element()
         self._write('<' + self._qname(name))
 
         for prefix, uri in self._undeclared_ns_maps:
@@ -142,18 +158,30 @@ class XMLGenerator(handler.ContentHandler):
 
         for (name, value) in attrs.items():
             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):
-        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):
-        self._write(escape(content))
+        if content:
+            self._finish_pending_start_element()
+            self._write(escape(content))
 
     def ignorableWhitespace(self, content):
-        self._write(content)
+        if content:
+            self._finish_pending_start_element()
+            self._write(content)
 
     def processingInstruction(self, target, data):
+        self._finish_pending_start_element()
         self._write('<?%s %s?>' % (target, data))
 
 
index fa3725a8da7a2e5da8ce80b091259ef5f57089f5..188947833ef40f3329ad85e86263550717abe4c8 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -34,6 +34,9 @@ Core and Builtins
 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
   switch to python-config for command line access.