Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 61 additions & 50 deletions json2xml/dicttoxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,18 @@
Items with a `None` type become empty XML elements.
This module works with Python 3.7+
"""

import datetime
import logging
import numbers
import os
from collections.abc import Callable, Sequence
from random import randint
from typing import Any, Dict, List, Optional, Tuple, Union

from defusedxml.minidom import parseString

LOG = logging.getLogger("dicttoxml")
DEBUGMODE = os.getenv("DEBUGMODE", False) # pragma: no cover
LOG = logging.getLogger("dicttoxml") # pragma: no cover


ids: List[str] = [] # initialize list of unique ids
Expand Down Expand Up @@ -100,7 +101,8 @@ def make_attrstring(attr: dict[str, Any]) -> str:

def key_is_valid_xml(key: str) -> bool:
"""Checks that a key is a valid XML name"""
LOG.info(f'Inside key_is_valid_xml(). Testing "{str(key)}"')
if DEBUGMODE: # pragma: no cover
LOG.info(f'Inside key_is_valid_xml(). Testing "{str(key)}"')
test_xml = f'<?xml version="1.0" encoding="UTF-8" ?><{key}>foo</{key}>'
try:
parseString(test_xml)
Expand All @@ -111,9 +113,10 @@ def key_is_valid_xml(key: str) -> bool:

def make_valid_xml_name(key: str, attr: Dict[str, Any]) -> Tuple[str, Dict[str, Any]]:
"""Tests an XML name and fixes it if invalid"""
LOG.info(
f'Inside make_valid_xml_name(). Testing key "{str(key)}" with attr "{str(attr)}"'
)
if DEBUGMODE: # pragma: no cover
LOG.info(
f'Inside make_valid_xml_name(). Testing key "{str(key)}" with attr "{str(attr)}"'
)
key = escape_xml(key)
# nothing happens at escape_xml if attr is not a string, we don't
# need to pass it to the method at all.
Expand Down Expand Up @@ -162,10 +165,11 @@ def convert(
) -> str:
"""Routes the elements of an object to the right function to convert them
based on their data type"""
LOG.info(f'Inside convert(). type(obj)="{type(obj).__name__}"')
# avoid cpu consuming object serialization => extra if
if LOG.getEffectiveLevel() <= logging.DEBUG:
LOG.debug(f' obj="{str(obj)}"')
if DEBUGMODE: # pragma: no cover
LOG.info(f'Inside convert(). type(obj)="{type(obj).__name__}"')
# avoid cpu consuming object serialization => extra if
if LOG.getEffectiveLevel() <= logging.DEBUG:
LOG.debug(f' obj="{str(obj)}"')

item_name = item_func(parent)
# since bool is also a subtype of number.Number and int, the check for bool
Expand Down Expand Up @@ -227,12 +231,13 @@ def dict2xml_str(
parse dict2xml
"""
keys_str = ", ".join(str(key) for key in item)
LOG.info(
f'Inside dict_item2xml_str: type(obj)="{type(item).__name__}", keys="{keys_str}"'
)
# avoid cpu consuming object serialization => extra if
if LOG.getEffectiveLevel() <= logging.DEBUG:
LOG.debug(f' item="{str(item)}"')
if DEBUGMODE: # pragma: no cover
LOG.info(
f'Inside dict_item2xml_str: type(obj)="{type(item).__name__}", keys="{keys_str}"'
)
# avoid cpu consuming object serialization => extra if
if LOG.getEffectiveLevel() <= logging.DEBUG:
LOG.debug(f' item="{str(item)}"')

if attr_type:
attr["type"] = get_xml_type(item)
Expand Down Expand Up @@ -292,22 +297,24 @@ def convert_dict(
) -> str:
"""Converts a dict into an XML string."""
keys_str = ", ".join(str(key) for key in obj)
LOG.info(
f'Inside convert_dict(): type(obj)="{type(obj).__name__}", keys="{keys_str}"'
)
# avoid cpu consuming object serialization => extra if
if LOG.getEffectiveLevel() <= logging.DEBUG:
LOG.debug(f' obj="{str(obj)}"')
if DEBUGMODE: # pragma: no cover
LOG.info(
f'Inside convert_dict(): type(obj)="{type(obj).__name__}", keys="{keys_str}"'
)
# avoid cpu consuming object serialization => extra if
if LOG.getEffectiveLevel() <= logging.DEBUG:
LOG.debug(f' obj="{str(obj)}"')

output: List[str] = []
addline = output.append

for key, val in obj.items():
LOG.info(
f'Looping inside convert_dict(): key="{str(key)}", type(val)="{type(val).__name__}"'
)
if LOG.getEffectiveLevel() <= logging.DEBUG:
LOG.debug(f' val="{str(val)}"')
if DEBUGMODE: # pragma: no cover
LOG.info(
f'Looping inside convert_dict(): key="{str(key)}", type(val)="{type(val).__name__}"'
)
if LOG.getEffectiveLevel() <= logging.DEBUG:
LOG.debug(f' val="{str(val)}"')

attr = {} if not ids else {"id": f"{get_unique_id(parent)}"}

Expand Down Expand Up @@ -377,10 +384,11 @@ def convert_list(
item_wrap: bool,
) -> str:
"""Converts a list into an XML string."""
LOG.info(f'Inside convert_list(): type(items)="{type(items).__name__}"')
# avoid cpu consuming object serialization => extra if
if LOG.getEffectiveLevel() <= logging.DEBUG:
LOG.debug(f' items="{str(items)}"')
if DEBUGMODE: # pragma: no cover
LOG.info(f'Inside convert_list(): type(items)="{type(items).__name__}"')
# avoid cpu consuming object serialization => extra if
if LOG.getEffectiveLevel() <= logging.DEBUG:
LOG.debug(f' items="{str(items)}"')

output: List[str] = []
addline = output.append
Expand All @@ -393,12 +401,13 @@ def convert_list(
this_id = get_unique_id(parent)

for i, item in enumerate(items):
LOG.info(
f'Looping inside convert_list(): index="{str(i)}", type="{type(item).__name__}"'
)
# avoid cpu consuming object serialization => extra if
if LOG.getEffectiveLevel() <= logging.DEBUG:
LOG.debug(f' item="{str(item)}"')
if DEBUGMODE: # pragma: no cover
LOG.info(
f'Looping inside convert_list(): index="{str(i)}", type="{type(item).__name__}"'
)
# avoid cpu consuming object serialization => extra if
if LOG.getEffectiveLevel() <= logging.DEBUG:
LOG.debug(f' item="{str(item)}"')

attr = {} if not ids else {"id": f"{this_id}_{i + 1}"}

Expand Down Expand Up @@ -474,9 +483,10 @@ def convert_kv(
cdata: bool = False,
) -> str:
"""Converts a number or string into an XML element"""
LOG.info(
f'Inside convert_kv(): key="{str(key)}", val="{str(val)}", type(val) is: "{type(val).__name__}"'
)
if DEBUGMODE: # pragma: no cover
LOG.info(
f'Inside convert_kv(): key="{str(key)}", val="{str(val)}", type(val) is: "{type(val).__name__}"'
)
key, attr = make_valid_xml_name(key, attr)

if attr_type:
Expand All @@ -489,9 +499,10 @@ def convert_bool(
key: str, val: bool, attr_type: bool, attr: Dict[str, Any] = {}, cdata: bool = False
) -> str:
"""Converts a boolean into an XML element"""
LOG.info(
f'Inside convert_bool(): key="{str(key)}", val="{str(val)}", type(val) is: "{type(val).__name__}"'
)
if DEBUGMODE: # pragma: no cover
LOG.info(
f'Inside convert_bool(): key="{str(key)}", val="{str(val)}", type(val) is: "{type(val).__name__}"'
)
key, attr = make_valid_xml_name(key, attr)

if attr_type:
Expand All @@ -504,7 +515,6 @@ def convert_none(
key: str, attr_type: bool, attr: Dict[str, Any] = {}, cdata: bool = False
) -> str:
"""Converts a null value into an XML element"""
# LOG.info(f'Inside convert_none(): key="{str(key)}" val={type(val)}')
key, attr = make_valid_xml_name(key, attr)

if attr_type:
Expand Down Expand Up @@ -554,12 +564,13 @@ def dicttoxml(
{'list': {'@attrs': {'a':'b','c':'d'}, '@val': [4, 5, 6]}
which results in <list a="b" c="d"><item>4</item><item>5</item><item>6</item></list>
"""
LOG.info(
f'Inside dicttoxml(): type(obj) is: "{type(obj).__name__}", type(ids") is : {type(ids).__name__}'
)
# avoid cpu consuming object serialization (problem for large objects) => extra if
if LOG.getEffectiveLevel() <= logging.DEBUG:
LOG.debug(f' obj="{str(obj)}"')
if DEBUGMODE: # pragma: no cover
LOG.info(
f'Inside dicttoxml(): type(obj) is: "{type(obj).__name__}", type(ids") is : {type(ids).__name__}'
)
# avoid cpu consuming object serialization (problem for large objects) => extra if
if LOG.getEffectiveLevel() <= logging.DEBUG:
LOG.debug(f' obj="{str(obj)}"')

output = []
namespacestr = ""
Expand Down
11 changes: 0 additions & 11 deletions tests/test_json2xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,6 @@ def test_item_wrap(self):
xmldata = json2xml.Json2xml(data, pretty=False).to_xml()
old_dict = xmltodict.parse(xmldata)
# item must be present within my_items
print(xmldata)
assert "item" in old_dict['all']['my_items']
assert "item" in old_dict['all']['my_str_items']

Expand All @@ -109,7 +108,6 @@ def test_no_item_wrap(self):
xmldata = json2xml.Json2xml(data, pretty=False, item_wrap=False).to_xml()
old_dict = xmltodict.parse(xmldata)
# my_item must be present within my_items
print(xmldata)
assert "my_item" in old_dict['all']['my_items']
assert "my_str_items" in old_dict['all']

Expand All @@ -119,7 +117,6 @@ def test_empty_array(self):
)
xmldata = json2xml.Json2xml(data, pretty=False).to_xml()
old_dict = xmltodict.parse(xmldata)
print(xmldata)
# item empty_list be present within all
assert "empty_list" in old_dict['all']

Expand All @@ -129,7 +126,6 @@ def test_attrs(self):
)
xmldata = json2xml.Json2xml(data, pretty=False).to_xml()
old_dict = xmltodict.parse(xmldata)
print(xmldata)
# test all attrs
assert "str" == old_dict['all']['my_string']['@type']
assert "int" == old_dict['all']['my_int']['@type']
Expand Down Expand Up @@ -181,9 +177,7 @@ def test_read_boolean_data_from_json(self):
"""Test correct return for boolean types."""
data = readfromjson("examples/booleanjson.json")
result = json2xml.Json2xml(data).to_xml()
print(result)
dict_from_xml = xmltodict.parse(result)
print(dict_from_xml)
assert dict_from_xml["all"]["boolean"]["#text"] != 'True'
assert dict_from_xml["all"]["boolean"]["#text"] == 'true'
assert dict_from_xml["all"]["boolean_dict_list"]["item"][0]["boolean_dict"]["boolean"]["#text"] == 'true'
Expand All @@ -195,9 +189,7 @@ def test_read_boolean_data_from_json2(self):
"""Test correct return for boolean types."""
data = readfromjson("examples/booleanjson2.json")
result = json2xml.Json2xml(data).to_xml()
print(result)
dict_from_xml = xmltodict.parse(result)
print(dict_from_xml)
assert dict_from_xml["all"]["boolean_list"]["item"][0]["#text"] != 'True'
assert dict_from_xml["all"]["boolean_list"]["item"][0]["#text"] == 'true'
assert dict_from_xml["all"]["boolean_list"]["item"][1]["#text"] == 'false'
Expand All @@ -212,7 +204,6 @@ def test_dict2xml_with_namespaces(self):
data = {'ns1:node1': 'data in namespace 1', 'ns2:node2': 'data in namespace 2'}
namespaces = {'ns1': 'http://www.google.de/ns1', 'ns2': 'http://www.google.de/ns2'}
result = dicttoxml.dicttoxml(data, attr_type=False, xml_namespaces=namespaces)
print(result)
assert b'<?xml version="1.0" encoding="UTF-8" ?>' \
b'<root xmlns:ns1="http://www.google.de/ns1" xmlns:ns2="http://www.google.de/ns2">' \
b'<ns1:node1>data in namespace 1</ns1:node1>' \
Expand All @@ -222,7 +213,6 @@ def test_dict2xml_with_namespaces(self):
def test_dict2xml_with_flat(self):
data = {'flat_list@flat': [1, 2, 3], 'non_flat_list': [4, 5, 6]}
result = dicttoxml.dicttoxml(data, attr_type=False)
print(result)
assert b'<?xml version="1.0" encoding="UTF-8" ?>' \
b'<root><item>1</item><item>2</item><item>3</item>' \
b'<non_flat_list><item>4</item><item>5</item><item>6</item></non_flat_list>' \
Expand All @@ -232,7 +222,6 @@ def test_dict2xml_with_val_and_custom_attr(self):
# in order to use @attr in non-dict objects, we need to lift into a dict and combine with @val as key
data = {'list1': [1, 2, 3], 'list2': {'@attrs': {'myattr1': 'myval1', 'myattr2': 'myval2'}, '@val': [4, 5, 6]}}
result = dicttoxml.dicttoxml(data, attr_type=False)
print(result)
assert b'<?xml version="1.0" encoding="UTF-8" ?>' \
b'<root><list1><item>1</item><item>2</item><item>3</item></list1>' \
b'<list2 myattr1="myval1" myattr2="myval2"><item>4</item><item>5</item><item>6</item></list2>' \
Expand Down
Loading