1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
|
from datetime import datetime
from random import randint
from numbers import Number
from html import escape
from secrets import token_hex
from typing import Dict
from xml.dom.minidom import parseString
from logging import getLogger, DEBUG, ERROR, FileHandler, StreamHandler
logger = getLogger("dicttoxml")
def set_debug(debug: bool = True, filename: str = 'dicttoxml.log'):
"""Kept for BC reasons."""
if debug:
logger.setLevel(DEBUG)
if len(logger.handlers) == 1:
logger.addHandler(
FileHandler(filename)
)
logger.addHandler(
StreamHandler()
)
logger.debug(f'\nLogging session starts: {datetime.now().isoformat()}')
else:
logger.debug('Debug mode is off.')
logger.setLevel(ERROR)
def make_id(element, start=100000, end=999999):
"""Returns a random integer. Kept for BC reasons."""
return f"{element}_{randint(start, end)}"
def get_unique_id(element):
"""Returns a unique id for a given element"""
return f"{element}_{token_hex(16)}"
def get_xml_type(val):
"""Returns the data type for the xml type attribute"""
if val is None:
return 'null'
if isinstance(val, (str, int, float, bool, dict, list)):
return type(val).__name__
if isinstance(val, Number):
return 'number'
return type(val).__name__
def escape_xml(s: str):
"""Kept for BC reasons"""
return escape(s) if isinstance(s, str) else s
def make_attrstring(attr):
"""Returns an attribute string in the form key="val" """
attrstring = ' '.join(['%s="%s"' % (k, v) for k, v in attr.items()])
return '%s%s' % (' ' if attrstring != '' else '', attrstring)
def key_is_valid_xml(key):
"""Checks that a key is a valid XML name"""
logger.debug('Inside key_is_valid_xml(). Testing "%s"' % (key))
test_xml = '<?xml version="1.0" encoding="UTF-8" ?><%s>foo</%s>' % (key, key)
try:
parseString(test_xml)
return True
except: # minidom does not implement exceptions well
return False
def make_valid_xml_name(key: str, attr: Dict[str, str]):
"""Tests an XML name and fixes it if invalid"""
logger.debug('Inside make_valid_xml_name(). Testing key "%s" with attr "%s"' % (key, attr))
key = escape_xml(key)
# pass through if key is already valid
if key_is_valid_xml(key):
return key, attr
# prepend a lowercase n if the key is numeric
if isinstance(key, int) or key.isdigit():
return 'n%s' % (key), attr
# replace spaces with underscores if that fixes the problem
if key_is_valid_xml(key.replace(' ', '_')):
return key.replace(' ', '_'), attr
# key is still invalid - move it into a name attribute
attr['name'] = key
key = 'key'
return key, attr
def wrap_cdata(s):
"""Wraps a string into CDATA sections"""
s = str(s).replace(']]>', ']]]]><![CDATA[>')
return '<![CDATA[' + s + ']]>'
def default_item_func(parent) -> str:
return 'item'
|