diff --git a/.coverage b/.coverage
index 672c021..a667c01 100644
Binary files a/.coverage and b/.coverage differ
diff --git a/.gitignore b/.gitignore
index b97131e..06591d5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -129,3 +129,5 @@ dmypy.json
.pyre/
.idea/
+.coverage
+junit.xml
diff --git a/json2xml/dicttoxml.py b/json2xml/dicttoxml.py
index d51d98b..538e765 100644
--- a/json2xml/dicttoxml.py
+++ b/json2xml/dicttoxml.py
@@ -127,7 +127,7 @@ def make_attrstring(attr: dict[str, Any]) -> str:
Returns:
str: The string of XML attributes.
"""
- attrstring = " ".join([f'{k}="{v}"' for k, v in attr.items()])
+ attrstring = " ".join([f'{k}="{escape_xml(v)}"' for k, v in attr.items()])
return f'{" " if attrstring != "" else ""}{attrstring}'
diff --git a/junit.xml b/junit.xml
index fef39a8..0b3139e 100644
--- a/junit.xml
+++ b/junit.xml
@@ -1 +1,33 @@
-
\ No newline at end of file
+self = <tests.test_xml_escape.TestEscaping object at 0x1051b4aa0>
+
+ def test_escapes_angle_brackets(self):
+ json_data = json.dumps({"root": {"@attrs": {"HelpText": "version <here>"}}})
+ result = json2xml.Json2xml(json_data).to_xml()
+> assert '"HelpText": "version <here>"' in result
+E assert '"HelpText": "version <here>"' in '<?xml version="1.0" encoding="UTF-8"?>\n<all>\n\t<item type="str">{"root": {"@attrs": {"HelpText": "version <here>"}}}</item>\n</all>\n'
+
+tests/test_xml_escape.py:39: AssertionErrorself = <tests.test_xml_escape.TestEscaping object at 0x1051b4740>
+
+ def test_escapes_quotes(self):
+ json_data = json.dumps({"root": {"@attrs": {"Text": "\"quoted\""}}})
+ result = json2xml.Json2xml(json_data).to_xml()
+> assert '"Text": "\\"quoted\\""' in result
+E assert '"Text": "\\"quoted\\""' in '<?xml version="1.0" encoding="UTF-8"?>\n<all>\n\t<item type="str">{"root": {"@attrs": {"Text": "\\"quoted\\""}}}</item>\n</all>\n'
+
+tests/test_xml_escape.py:44: AssertionErrorself = <tests.test_xml_escape.TestEscaping object at 0x1051b42f0>
+
+ def test_escapes_ampersands(self):
+ json_data = json.dumps({"root": {"@attrs": {"Text": "this & that"}}})
+ result = json2xml.Json2xml(json_data).to_xml()
+> assert '"Text": "this & that"' in result
+E assert '"Text": "this & that"' in '<?xml version="1.0" encoding="UTF-8"?>\n<all>\n\t<item type="str">{"root": {"@attrs": {"Text": "this & that"}}}</item>\n</all>\n'
+
+tests/test_xml_escape.py:49: AssertionErrorself = <tests.test_xml_escape.TestEscaping object at 0x1051b5730>
+
+ def test_escapes_mixed_special_chars(self):
+ json_data = json.dumps({"root": {"@attrs": {"Text": "<tag> & \"quote\""}}})
+ result = json2xml.Json2xml(json_data).to_xml()
+> assert '"Text": "<tag> & \\"quote\\""' in result
+E assert '"Text": "<tag> & \\"quote\\""' in '<?xml version="1.0" encoding="UTF-8"?>\n<all>\n\t<item type="str">{"root": {"@attrs": {"Text": "<tag> & \\"quote\\""}}}</item>\n</all>\n'
+
+tests/test_xml_escape.py:54: AssertionError
\ No newline at end of file
diff --git a/tests/test_dict2xml.py b/tests/test_dict2xml.py
index 7a54ab7..21d5bd5 100644
--- a/tests/test_dict2xml.py
+++ b/tests/test_dict2xml.py
@@ -3,7 +3,7 @@
import pytest
-from json2xml import dicttoxml
+from json2xml import dicttoxml, json2xml
class TestDict2xml:
diff --git a/tests/test_xml_escape.py b/tests/test_xml_escape.py
new file mode 100644
index 0000000..8592641
--- /dev/null
+++ b/tests/test_xml_escape.py
@@ -0,0 +1,56 @@
+import pytest
+import json
+
+from json2xml import dicttoxml, json2xml
+
+
+
+class TestEscaping:
+ def test_escaping(self):
+ # Test cases
+ test_cases = [
+ {
+ "root": {
+ "@attrs": {
+ "Text": "this & that"
+ }
+ }
+ },
+ {
+ "data": json.dumps({"key": "value & more"})
+ },
+ {
+ "mixed": {
+ "json_str": json.dumps({"a": "b & c"}),
+ "plain": "text & symbols"
+ }
+ }
+ ]
+ for i, data in enumerate(test_cases):
+ print(f"\nTest case {i + 1}:")
+ print("Input:", data)
+ xml = dicttoxml.dicttoxml(data, custom_root='all')
+ print("Output XML:")
+ print(xml.decode('utf-8'))
+
+ def test_escapes_angle_brackets(self):
+ json_data = json.dumps({"root": {"@attrs": {"HelpText": "version "}}})
+ result = json2xml.Json2xml(json_data).to_xml()
+ assert '"HelpText": "version <here>"' in result
+
+ def test_escapes_quotes(self):
+ json_data = json.dumps({"root": {"@attrs": {"Text": "\"quoted\""}}})
+ result = json2xml.Json2xml(json_data).to_xml()
+ assert '"Text": "\\"quoted\\""' in result
+
+ def test_escapes_ampersands(self):
+ json_data = json.dumps({"root": {"@attrs": {"Text": "this & that"}}})
+ result = json2xml.Json2xml(json_data).to_xml()
+ assert '"Text": "this & that"' in result
+
+ def test_escapes_mixed_special_chars(self):
+ json_data = json.dumps({"root": {"@attrs": {"Text": " & \"quote\""}}})
+ result = json2xml.Json2xml(json_data).to_xml()
+ assert '"Text": "<tag> & \\"quote\\""' in result
+
+