@@ -1062,3 +1062,84 @@ def test_convert_dict_with_falsy_value_line_400(self) -> None:
10621062
10631063 # None should trigger the "elif not val:" branch and result in an empty element
10641064 assert "<none_key></none_key>" == result
1065+
1066+ def test_attrs_xml_escaping (self ) -> None :
1067+ """Test that @attrs values are properly XML-escaped."""
1068+ # Test the specific case from the user's bug report
1069+ info_dict = {
1070+ 'Info' : {
1071+ "@attrs" : {
1072+ "Name" : "systemSpec" ,
1073+ "HelpText" : "spec version <here>"
1074+ }
1075+ }
1076+ }
1077+ result = dicttoxml .dicttoxml (info_dict , attr_type = False , item_wrap = False , root = False ).decode ('utf-8' )
1078+ expected = '<Info Name="systemSpec" HelpText="spec version <here>"></Info>'
1079+ assert expected == result
1080+
1081+ def test_attrs_comprehensive_xml_escaping (self ) -> None :
1082+ """Test comprehensive XML escaping in attributes."""
1083+ data = {
1084+ 'Element' : {
1085+ "@attrs" : {
1086+ "ampersand" : "Tom & Jerry" ,
1087+ "less_than" : "value < 10" ,
1088+ "greater_than" : "value > 5" ,
1089+ "quotes" : 'He said "Hello"' ,
1090+ "single_quotes" : "It's working" ,
1091+ "mixed" : "Tom & Jerry < 10 > 5 \" quoted\" 'apostrophe'"
1092+ },
1093+ "@val" : "content"
1094+ }
1095+ }
1096+ result = dicttoxml .dicttoxml (data , attr_type = False , item_wrap = False , root = False ).decode ('utf-8' )
1097+
1098+ # Check that all special characters are properly escaped in attributes
1099+ assert 'ampersand="Tom & Jerry"' in result
1100+ assert 'less_than="value < 10"' in result
1101+ assert 'greater_than="value > 5"' in result
1102+ assert 'quotes="He said "Hello""' in result
1103+ assert 'single_quotes="It's working"' in result
1104+ assert 'mixed="Tom & Jerry < 10 > 5 "quoted" 'apostrophe'"' in result
1105+
1106+ # Verify the element content is also properly escaped
1107+ assert ">content<" in result
1108+
1109+ def test_attrs_empty_and_none_values (self ) -> None :
1110+ """Test attribute handling with empty and None values."""
1111+ data = {
1112+ 'Element' : {
1113+ "@attrs" : {
1114+ "empty" : "" ,
1115+ "zero" : 0 ,
1116+ "false" : False
1117+ }
1118+ }
1119+ }
1120+ result = dicttoxml .dicttoxml (data , attr_type = False , item_wrap = False , root = False ).decode ('utf-8' )
1121+
1122+ assert 'empty=""' in result
1123+ assert 'zero="0"' in result
1124+ assert 'false="False"' in result
1125+
1126+ def test_make_attrstring_function_directly (self ) -> None :
1127+ """Test the make_attrstring function directly."""
1128+ from json2xml .dicttoxml import make_attrstring
1129+
1130+ # Test basic escaping
1131+ attrs = {
1132+ "test" : "value <here>" ,
1133+ "ampersand" : "Tom & Jerry" ,
1134+ "quotes" : 'Say "hello"'
1135+ }
1136+ result = make_attrstring (attrs )
1137+
1138+ assert 'test="value <here>"' in result
1139+ assert 'ampersand="Tom & Jerry"' in result
1140+ assert 'quotes="Say "hello""' in result
1141+
1142+ # Test empty attributes
1143+ empty_attrs : dict [str , Any ] = {}
1144+ result = make_attrstring (empty_attrs )
1145+ assert result == ""
0 commit comments