88
88
"XMLParser" , "XMLPullParser" ,
89
89
"register_namespace" ,
90
90
"canonicalize" , "C14NWriterTarget" ,
91
- ]
91
+ "XMLDeclarationQuotes" ,
92
+ "ShortEmptyElements"
93
+ ]
92
94
93
95
VERSION = "1.3.0"
94
96
99
101
import collections
100
102
import collections .abc
101
103
import contextlib
104
+ import enum
102
105
103
106
from . import ElementPath
104
107
@@ -508,6 +511,58 @@ def __eq__(self, other):
508
511
509
512
# --------------------------------------------------------------------
510
513
514
+ class XMLDeclarationQuotes (enum .Enum ):
515
+ """
516
+ Whether or not single quotes or double quotes ought to be used in the XML
517
+ declaration.
518
+
519
+ *SINGLE* (default): <?xml version='1.0' encoding='UTF-8'?>
520
+ *DOUBLE*: <?xml version="1.0" encoding="UTF-8"?>
521
+ """
522
+ SINGLE = "'"
523
+ DOUBLE = '"'
524
+
525
+ def __str__ (self ):
526
+ return self .value
527
+
528
+ class ShortEmptyElements (enum .Enum ):
529
+ """
530
+ This class creates backwards compatibility with the boolean value of
531
+ *short_empty_elements* that existed prior to 3.??.
532
+
533
+ Assuming the tag `<q/>`, the results will be:
534
+
535
+ *SPACE* (default): `<q />`
536
+ *NOSPACE*: `<q/>`
537
+ *NONE*: `<q></q>`
538
+ """
539
+ SPACE = " "
540
+ NOSPACE = ""
541
+ NONE = False
542
+
543
+ def __bool__ (self ):
544
+ return self != ShortEmptyElements .NONE
545
+
546
+ @classmethod
547
+ def _missing_ (cls , value ):
548
+ if value is enum .no_arg :
549
+ return cls .SPACE
550
+ elif isinstance (value , bool ):
551
+ return cls .SPACE if value else cls .NONE
552
+ else :
553
+ return super ()._missing_ (value )
554
+
555
+ @classmethod
556
+ def tag_defaultdict (cls , short_empty_elements ):
557
+ if not isinstance (short_empty_elements , collections .defaultdict ):
558
+ if isinstance (short_empty_elements , ShortEmptyElements ):
559
+ return collections .defaultdict (lambda : short_empty_elements )
560
+ elif bool (short_empty_elements ) is True :
561
+ return collections .defaultdict (lambda : ShortEmptyElements .SPACE )
562
+ else :
563
+ return collections .defaultdict (lambda : ShortEmptyElements .NONE )
564
+ else :
565
+ return short_empty_elements
511
566
512
567
class ElementTree :
513
568
"""An XML element hierarchy.
@@ -680,6 +735,7 @@ def iterfind(self, path, namespaces=None):
680
735
def write (self , file_or_filename ,
681
736
encoding = None ,
682
737
xml_declaration = None ,
738
+ xml_declaration_quotes = XMLDeclarationQuotes .SINGLE ,
683
739
default_namespace = None ,
684
740
method = None , * ,
685
741
short_empty_elements = True ):
@@ -695,6 +751,9 @@ def write(self, file_or_filename,
695
751
is added if encoding IS NOT either of:
696
752
US-ASCII, UTF-8, or Unicode
697
753
754
+ *xml_declaration_quotes* -- Changes character used in XML declaration,
755
+ see *XMLDeclarationQuotes*.
756
+
698
757
*default_namespace* -- sets the default XML namespace (for "xmlns")
699
758
700
759
*method* -- either "xml" (default), "html, "text", or "c14n"
@@ -703,8 +762,12 @@ def write(self, file_or_filename,
703
762
that contain no content. If True (default)
704
763
they are emitted as a single self-closed
705
764
tag, otherwise they are emitted as a pair
706
- of start/end tags
765
+ of start/end tags.
707
766
767
+ For more control, can be a
768
+ *ShortEmptyElements* object, or a
769
+ defaultdict keyed by tags as strings and
770
+ valued with such objects.
708
771
"""
709
772
if not method :
710
773
method = "xml"
@@ -720,13 +783,16 @@ def write(self, file_or_filename,
720
783
(xml_declaration is None and
721
784
encoding .lower () != "unicode" and
722
785
declared_encoding .lower () not in ("utf-8" , "us-ascii" ))):
723
- write ("<?xml version='1.0' encoding='%s'?>\n " % (
724
- declared_encoding ,))
786
+ write ("<?xml version={0}1.0{0} encoding={0}{1}{0}?>\n "
787
+ .format (xml_declaration_quotes , declared_encoding ))
788
+ if not isinstance (xml_declaration_quotes , XMLDeclarationQuotes ):
789
+ raise ValueError ("Unknown type for `xml_declaration_quotes`" )
725
790
if method == "text" :
726
791
_serialize_text (write , self ._root )
727
792
else :
728
793
qnames , namespaces = _namespaces (self ._root , default_namespace )
729
794
serialize = _serialize [method ]
795
+ short_empty_elements = ShortEmptyElements .tag_defaultdict (short_empty_elements )
730
796
serialize (write , self ._root , qnames , namespaces ,
731
797
short_empty_elements = short_empty_elements )
732
798
@@ -885,7 +951,7 @@ def _serialize_xml(write, elem, qnames, namespaces,
885
951
else :
886
952
v = _escape_attrib (v )
887
953
write (" %s=\" %s\" " % (qnames [k ], v ))
888
- if text or len (elem ) or not short_empty_elements :
954
+ if text or len (elem ) or not bool ( short_empty_elements [ tag ]) :
889
955
write (">" )
890
956
if text :
891
957
write (_escape_cdata (text ))
@@ -894,7 +960,7 @@ def _serialize_xml(write, elem, qnames, namespaces,
894
960
short_empty_elements = short_empty_elements )
895
961
write ("</" + tag + ">" )
896
962
else :
897
- write (" />" )
963
+ write (short_empty_elements [ tag ]. value + " />" )
898
964
if elem .tail :
899
965
write (_escape_cdata (elem .tail ))
900
966
0 commit comments