diff --git a/changelogs/fragments/to_xml_indent_with_spaces.yaml b/changelogs/fragments/to_xml_indent_with_spaces.yaml
new file mode 100644
index 0000000..c06a3cb
--- /dev/null
+++ b/changelogs/fragments/to_xml_indent_with_spaces.yaml
@@ -0,0 +1,5 @@
+---
+minor_changes:
+ - >-
+ to_xml - Added support for using spaces to indent an XML doc via a new
+ `indent` parameter.
diff --git a/docs/ansible.utils.to_xml_filter.rst b/docs/ansible.utils.to_xml_filter.rst
index 40d3490..022fcb2 100644
--- a/docs/ansible.utils.to_xml_filter.rst
+++ b/docs/ansible.utils.to_xml_filter.rst
@@ -73,6 +73,47 @@ Parameters
Conversion library to use within the filter plugin.
+
+
+
+ indent
+
+
+ string
+
+ |
+
+ Choices:
+ tabs ←
+ - spaces
+
+ |
+
+ |
+
+ The character used for indentation (defaults to tabs).
+ |
+
+
+
+
+ indent_width
+
+
+ integer
+
+ |
+
+ Default:
4
+ |
+
+ |
+
+ The number of spaces to use to indent output data.
+ This option is only used when indent="spaces", otherwise it is ignored.
+ When indent="tabs", a single tab is always used for indentation.
+ |
+
@@ -149,6 +190,38 @@ Examples
# Cisco-IOS-XR-ifmgr-cfg\">\n\t\n"
# }
+ #### example3 with indent='spaces' and indent_width=2
+
+ - name: Define JSON data
+ ansible.builtin.set_fact:
+ data:
+ "interface-configurations":
+ "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg"
+ "interface-configuration":
+ - debug:
+ msg: "{{ data|ansible.utils.to_xml(indent='spaces', indent_width=2) }}"
+
+ # TASK [Define JSON data ] *************************************************************************
+ # task path: /Users/amhatre/ansible-collections/playbooks/test_utils_json_to_xml.yaml:5
+ # ok: [localhost] => {
+ # "ansible_facts": {
+ # "data": {
+ # "interface-configurations": {
+ # "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg",
+ # "interface-configuration": null
+ # }
+ # }
+ # },
+ # "changed": false
+ # }
+ # TASK [debug] ***********************************************************************************************************
+ # task path: /Users/amhatre/ansible-collections/playbooks/test_utils_json_to_xml.yaml:13
+ # Loading collection ansible.utils from /Users/amhatre/ansible-collections/collections/ansible_collections/ansible/utils
+ # ok: [localhost] => {
+ # "\n\n \n"
+ # }
+
diff --git a/plugins/filter/to_xml.py b/plugins/filter/to_xml.py
index 555ba62..d8281e8 100644
--- a/plugins/filter/to_xml.py
+++ b/plugins/filter/to_xml.py
@@ -35,6 +35,19 @@ DOCUMENTATION = """
- Conversion library to use within the filter plugin.
type: str
default: xmltodict
+ indent:
+ description:
+ - The character used for indentation (defaults to tabs).
+ type: str
+ default: tabs
+ choices: ["tabs", "spaces"]
+ indent_width:
+ description:
+ - The number of spaces to use to indent output data.
+ - This option is only used when indent="spaces", otherwise it is ignored.
+ - When indent="tabs", a single tab is always used for indentation.
+ type: int
+ default: 4
"""
EXAMPLES = r"""
@@ -104,6 +117,37 @@ EXAMPLES = r"""
# Cisco-IOS-XR-ifmgr-cfg\">\n\t\n"
# }
+#### example3 with indent='spaces' and indent_width=2
+
+- name: Define JSON data
+ ansible.builtin.set_fact:
+ data:
+ "interface-configurations":
+ "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg"
+ "interface-configuration":
+- debug:
+ msg: "{{ data|ansible.utils.to_xml(indent='spaces', indent_width=2) }}"
+
+# TASK [Define JSON data ] *************************************************************************
+# task path: /Users/amhatre/ansible-collections/playbooks/test_utils_json_to_xml.yaml:5
+# ok: [localhost] => {
+# "ansible_facts": {
+# "data": {
+# "interface-configurations": {
+# "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg",
+# "interface-configuration": null
+# }
+# }
+# },
+# "changed": false
+# }
+# TASK [debug] ***********************************************************************************************************
+# task path: /Users/amhatre/ansible-collections/playbooks/test_utils_json_to_xml.yaml:13
+# Loading collection ansible.utils from /Users/amhatre/ansible-collections/collections/ansible_collections/ansible/utils
+# ok: [localhost] => {
+# "\n\n \n"
+# }
"""
from ansible.errors import AnsibleFilterError
@@ -123,7 +167,7 @@ except ImportError:
@pass_environment
def _to_xml(*args, **kwargs):
"""Convert the given data from json to xml."""
- keys = ["data", "engine"]
+ keys = ["data", "engine", "indent", "indent_width"]
data = dict(zip(keys, args[1:]))
data.update(kwargs)
aav = AnsibleArgSpecValidator(data=data, schema=DOCUMENTATION, name="to_xml")
diff --git a/plugins/plugin_utils/to_xml.py b/plugins/plugin_utils/to_xml.py
index 6004fa6..1c3f882 100644
--- a/plugins/plugin_utils/to_xml.py
+++ b/plugins/plugin_utils/to_xml.py
@@ -35,18 +35,26 @@ def _raise_error(msg):
raise AnsibleFilterError(error)
-def to_xml(data, engine):
+def to_xml(data, engine, indent, indent_width):
"""Convert data which is in json to xml"
:param data: The data passed in (data|to_xml(...))
:type data: xml
:param engine: Conversion library default=xmltodict
+ :param indent: Indent char default='tabs'
+ :param indent_width: Indent char multiplier default=4
"""
+
+ indent_char = "\t"
+
+ if indent == "spaces":
+ indent_char = " " * indent_width
+
if engine == "xmltodict":
if not HAS_XMLTODICT:
_raise_error("Missing required library xmltodict")
try:
- res = xmltodict.unparse(data, pretty=True)
+ res = xmltodict.unparse(data, pretty=True, indent=indent_char)
except Exception:
_raise_error("Input json is not valid")
return res
diff --git a/tests/unit/plugins/filter/test_to_xml.py b/tests/unit/plugins/filter/test_to_xml.py
index 81fd6a3..f1ce6fb 100644
--- a/tests/unit/plugins/filter/test_to_xml.py
+++ b/tests/unit/plugins/filter/test_to_xml.py
@@ -18,11 +18,21 @@ from ansible_collections.ansible.utils.plugins.filter.to_xml import _to_xml
INVALID_DATA = ''
VALID_DATA = {
- "interface-configurations": {"@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg"},
+ "interface-configurations": {
+ "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg",
+ "key1": "value1",
+ },
}
-OUTPUT = """
-"""
+OUTPUT_TABS = """
+
+\tvalue1
+"""
+
+OUTPUT_SPACES = """
+
+ value1
+"""
class TestToXml(unittest.TestCase):
@@ -44,7 +54,7 @@ class TestToXml(unittest.TestCase):
self.maxDiff = None
args = ["", VALID_DATA, "xmltodict"]
result = _to_xml(*args)
- self.assertEqual(result, OUTPUT)
+ self.assertEqual(result, OUTPUT_TABS)
def test_args(self):
"""Check passing invalid argspec"""
@@ -65,3 +75,23 @@ class TestToXml(unittest.TestCase):
with self.assertRaises(AnsibleError) as error:
_to_xml(*args, **kwargs)
self.assertIn("engine: test is not supported", str(error.exception))
+
+ def test_indent_with_spaces(self):
+ """Check passing indent with spaces and default indent_width"""
+ self.maxDiff = None
+ args = ["", VALID_DATA, "xmltodict", "spaces", 4]
+ result = _to_xml(*args)
+ self.assertEqual(result, OUTPUT_SPACES)
+
+ def test_invalid_indent(self):
+ """Check passing invalid indent value"""
+
+ # missing required arguments
+ args = ["", VALID_DATA, "xmltodict", "test"]
+ kwargs = {}
+ with self.assertRaises(AnsibleError) as error:
+ _to_xml(*args, **kwargs)
+ self.assertIn(
+ "value of indent must be one of: tabs, spaces, got: test",
+ str(error.exception),
+ )