2
2
"Makes working with XML feel like you are working with JSON"
3
3
4
4
from xml .parsers import expat
5
- from xml .sax .saxutils import XMLGenerator
5
+ from xml .sax .saxutils import XMLGenerator , escape
6
6
from xml .sax .xmlreader import AttributesImpl
7
7
from io import StringIO
8
8
@@ -459,7 +459,25 @@ def _emit(key, value, content_handler,
459
459
namespace_separator = ':' ,
460
460
namespaces = None ,
461
461
full_document = True ,
462
- expand_iter = None ):
462
+ expand_iter = None ,
463
+ comment_key = '#comment' ):
464
+ if isinstance (key , str ) and key == comment_key :
465
+ comments_list = value if isinstance (value , list ) else [value ]
466
+ if isinstance (indent , int ):
467
+ indent = " " * indent
468
+ for comment_text in comments_list :
469
+ if comment_text is None :
470
+ continue
471
+ comment_text = _convert_value_to_string (comment_text )
472
+ if comment_text == "" :
473
+ continue
474
+ if pretty :
475
+ content_handler .ignorableWhitespace (depth * indent )
476
+ content_handler .comment (comment_text )
477
+ if pretty :
478
+ content_handler .ignorableWhitespace (newl )
479
+ return
480
+
463
481
key = _process_namespace (key , namespaces , namespace_separator , attr_prefix )
464
482
if preprocessor is not None :
465
483
result = preprocessor (key , value )
@@ -519,7 +537,7 @@ def _emit(key, value, content_handler,
519
537
attr_prefix , cdata_key , depth + 1 , preprocessor ,
520
538
pretty , newl , indent , namespaces = namespaces ,
521
539
namespace_separator = namespace_separator ,
522
- expand_iter = expand_iter )
540
+ expand_iter = expand_iter , comment_key = comment_key )
523
541
if cdata is not None :
524
542
content_handler .characters (cdata )
525
543
if pretty and children :
@@ -529,8 +547,13 @@ def _emit(key, value, content_handler,
529
547
content_handler .ignorableWhitespace (newl )
530
548
531
549
550
+ class _XMLGenerator (XMLGenerator ):
551
+ def comment (self , text ):
552
+ self ._write (f"<!--{ escape (text )} -->" )
553
+
554
+
532
555
def unparse (input_dict , output = None , encoding = 'utf-8' , full_document = True ,
533
- short_empty_elements = False ,
556
+ short_empty_elements = False , comment_key = '#comment' ,
534
557
** kwargs ):
535
558
"""Emit an XML document for the given `input_dict` (reverse of `parse`).
536
559
@@ -546,21 +569,25 @@ def unparse(input_dict, output=None, encoding='utf-8', full_document=True,
546
569
can be customized with the `newl` and `indent` parameters.
547
570
548
571
"""
549
- if full_document and len (input_dict ) != 1 :
550
- raise ValueError ('Document must have exactly one root.' )
551
572
must_return = False
552
573
if output is None :
553
574
output = StringIO ()
554
575
must_return = True
555
576
if short_empty_elements :
556
- content_handler = XMLGenerator (output , encoding , True )
577
+ content_handler = _XMLGenerator (output , encoding , True )
557
578
else :
558
- content_handler = XMLGenerator (output , encoding )
579
+ content_handler = _XMLGenerator (output , encoding )
559
580
if full_document :
560
581
content_handler .startDocument ()
582
+ seen_root = False
561
583
for key , value in input_dict .items ():
562
- _emit (key , value , content_handler , full_document = full_document ,
563
- ** kwargs )
584
+ if key != comment_key and full_document and seen_root :
585
+ raise ValueError ("Document must have exactly one root." )
586
+ _emit (key , value , content_handler , full_document = full_document , comment_key = comment_key , ** kwargs )
587
+ if key != comment_key :
588
+ seen_root = True
589
+ if full_document and not seen_root :
590
+ raise ValueError ("Document must have exactly one root." )
564
591
if full_document :
565
592
content_handler .endDocument ()
566
593
if must_return :
0 commit comments