@@ -262,12 +262,17 @@ def dict2xml_str(
262262 parse dict2xml
263263 """
264264 ids : list [str ] = [] # initialize list of unique ids
265+ item = dict (item ) # copy to avoid modifying the original dict
265266 ", " .join (str (key ) for key in item )
266267 subtree = "" # Initialize subtree with default empty string
267268
268269 if attr_type :
269270 attr ["type" ] = get_xml_type (item )
270271 val_attr : dict [str , str ] = item .pop ("@attrs" , attr ) # update attr with custom @attr if exists
272+ # Handle other @ keys as attributes
273+ for key in list (item .keys ()):
274+ if key .startswith ('@' ) and key not in ('@val' , '@flat' , '@attrs' ):
275+ val_attr [key [1 :]] = item .pop (key )
271276 rawitem = item ["@val" ] if "@val" in item else item
272277 if is_primitive_type (rawitem ):
273278 if isinstance (rawitem , dict ):
@@ -522,7 +527,15 @@ def convert_kv(
522527 if attr_type :
523528 attr ["type" ] = get_xml_type (val )
524529 attr_string = make_attrstring (attr )
525- return f"<{ key } { attr_string } >{ wrap_cdata (val ) if cdata else escape_xml (val )} </{ key } >"
530+ val_str = str (val )
531+ if cdata :
532+ if '<![CDATA[' in val_str :
533+ content = val_str
534+ else :
535+ content = wrap_cdata (val )
536+ else :
537+ content = escape_xml (val )
538+ return f"<{ key } { attr_string } >{ content } </{ key } >"
526539
527540
528541def convert_bool (
@@ -566,7 +579,8 @@ def dicttoxml(
566579 list_headers : bool = False ,
567580 parallel : bool = False ,
568581 workers : int | None = None ,
569- chunk_size : int = 100
582+ chunk_size : int = 100 ,
583+ min_items_for_parallel : int = 10
570584) -> bytes :
571585 """
572586 Converts a python object into XML.
@@ -668,6 +682,10 @@ def dicttoxml(
668682 Default is 100
669683 Number of list items to process per chunk in parallel mode.
670684
685+ :param int min_items_for_parallel:
686+ Default is 10
687+ Minimum number of items in a dictionary to enable parallel processing.
688+
671689 Dictionaries-keys with special char '@' has special meaning:
672690 @attrs: This allows custom xml attributes:
673691
@@ -718,17 +736,61 @@ def dicttoxml(
718736 ns = xml_namespaces [prefix ]
719737 namespace_str += f' xmlns:{ prefix } ="{ ns } "'
720738
739+ def _dispatch_convert (
740+ obj , ids , parent ,
741+ attr_type , item_func , cdata , item_wrap , list_headers ,
742+ parallel , workers , chunk_size , min_items_for_parallel , xml_namespaces
743+ ):
744+ should_use_parallel = parallel
745+ if parallel :
746+ if cdata :
747+ should_use_parallel = False
748+ if isinstance (obj , dict ) and any (isinstance (k , str ) and k .startswith ('@' ) for k in obj .keys ()):
749+ should_use_parallel = False
750+ if xml_namespaces :
751+ should_use_parallel = False
752+ if should_use_parallel :
753+ if isinstance (obj , dict ):
754+ return convert_dict_parallel (
755+ obj , ids , parent ,
756+ attr_type = attr_type , item_func = item_func , cdata = cdata ,
757+ item_wrap = item_wrap , list_headers = list_headers ,
758+ workers = workers , min_items_for_parallel = min_items_for_parallel
759+ )
760+ if isinstance (obj , Sequence ) and not isinstance (obj , (str , bytes )):
761+ return convert_list_parallel (
762+ obj , ids , parent ,
763+ attr_type = attr_type , item_func = item_func , cdata = cdata ,
764+ item_wrap = item_wrap , list_headers = list_headers ,
765+ workers = workers , chunk_size = chunk_size
766+ )
767+ # fallback to serial
768+ return convert (
769+ obj , ids ,
770+ attr_type , item_func , cdata , item_wrap ,
771+ parent = parent , list_headers = list_headers
772+ )
773+
774+ should_use_parallel = parallel
721775 if parallel :
776+ if cdata :
777+ should_use_parallel = False
778+ if isinstance (obj , dict ) and any (isinstance (k , str ) and k .startswith ('@' ) for k in obj .keys ()):
779+ should_use_parallel = False
780+ if xml_namespaces :
781+ should_use_parallel = False
782+
783+ if should_use_parallel :
722784 from json2xml .parallel import convert_dict_parallel , convert_list_parallel
723785
724786 if root :
725787 output .append ('<?xml version="1.0" encoding="UTF-8" ?>' )
726788 if isinstance (obj , dict ):
727789 output_elem = convert_dict_parallel (
728790 obj , ids , custom_root , attr_type , item_func , cdata , item_wrap ,
729- list_headers = list_headers , workers = workers , min_items_for_parallel = 10
791+ list_headers = list_headers , workers = workers , min_items_for_parallel = min_items_for_parallel
730792 )
731- elif isinstance (obj , Sequence ):
793+ elif isinstance (obj , Sequence ) and not isinstance ( obj , ( str , bytes )) :
732794 output_elem = convert_list_parallel (
733795 obj , ids , custom_root , attr_type , item_func , cdata , item_wrap ,
734796 list_headers = list_headers , workers = workers , chunk_size = chunk_size
@@ -742,11 +804,11 @@ def dicttoxml(
742804 if isinstance (obj , dict ):
743805 output .append (
744806 convert_dict_parallel (
745- obj , ids , "" , attr_type , item_func , cdata , item_wrap ,
746- list_headers = list_headers , workers = workers , min_items_for_parallel = 10
807+ obj , ids , "" , attr_type , item_func , cdata , item_wrap ,
808+ list_headers = list_headers , workers = workers , min_items_for_parallel = min_items_for_parallel
747809 )
748810 )
749- elif isinstance (obj , Sequence ):
811+ elif isinstance (obj , Sequence ) and not isinstance ( obj , ( str , bytes )) :
750812 output .append (
751813 convert_list_parallel (
752814 obj , ids , "" , attr_type , item_func , cdata , item_wrap ,
0 commit comments