import xml.dom.minidom
import __main__
import logging
logger = logging.getLogger("openPypeline.xml")
[docs]
class xmlserialize(object): pass
[docs]
class xmlunpicklingException(Exception): pass
[docs]
class xmlfile(object):
""" Class to handle saving / loading an xmlserialize object to file """
def __init__(self, filename=None):
self.filename = filename
[docs]
def load(self):
''' Load xml from self.filename, return object '''
node = xml.dom.minidom.parse(self.filename)
return self.unpickle(node.documentElement)
[docs]
def save(self, obj):
''' Save node to self.filename '''
node = self.pickle(root=obj, fabric=xml.dom.minidom.Document())
with open(self.filename, 'w', encoding='utf-8') as f:
node.writexml(f)
# helper functions
[docs]
def getMod(self, cls):
'''
cls would be in the form like module1.module2.cls
Loops through __main__ to see how module1.module2 is imported
Returns the class(obj) using the existing import method
Returns None if the module or class isn't found
'''
import __main__
import sys
if '.' not in cls:
return None
# separate the module from the class [module1.module2].[cls]
logger.debug(f"Class: {cls}")
mod = cls[0:cls.rindex('.')]
className = cls[cls.rindex('.')+1:]
# Check sys.modules first for a safer and more direct lookup
if mod in sys.modules:
class_obj = getattr(sys.modules[mod], className, None)
if class_obj:
return class_obj()
for d in dir(__main__):
try:
module_obj = getattr(__main__, d)
mod_name = getattr(module_obj, '__name__', None)
if mod_name and f"{mod_name}.{className}" == cls:
class_obj = getattr(module_obj, className)
return class_obj()
except Exception:
pass # Ignore if no attribute __name__
return None
[docs]
def getType(self, obj):
""" Generates string representation of class of obj
discarding decoration """
return str(obj.__class__).split("'")[1]
_easyToPickle = [ "int", "float", "str", "unicode", "long" ]
def _isCallable(self, obj):
return hasattr(obj, "__call__")
#
# pickling
#
def _pickleDictItems(self, root, node, fabric):
for key, value in root.items():
tempnode = fabric.createElement("item")
tempnode.appendChild(self.pickle(key, fabric, "key"))
tempnode.appendChild(self.pickle(value, fabric, "value"))
node.appendChild(tempnode)
def _pickleListItems(self, root, node, fabric):
for idx, obj in enumerate(root):
tempnode = self.pickle(obj, fabric, "item")
tempnode.attributes["index"] = str(idx)
node.appendChild(tempnode)
_pickleTupleItems = _pickleListItems
[docs]
def pickle(self, root, fabric, elementName="root"):
node = fabric.createElement(elementName)
typeStr = self.getType(root)
node.attributes["type"]=typeStr
if isinstance(root, xmlserialize):
node = self._pickleObjectWithAttributes(node, root, fabric, elementName)
elif typeStr in self._easyToPickle:
node.appendChild(fabric.createTextNode(str(root)))
elif isinstance(root, dict):
self._pickleDictItems(root, node, fabric)
elif isinstance(root, list):
self._pickleListItems(root, node, fabric)
elif isinstance(root, tuple):
self._pickleTupleItems(root, node, fabric)
else:
# fallback handler
node.appendChild(fabric.createTextNode(repr(root)))
return node
def _pickleObjectWithAttributes(self, node, root, fabric, elementName):
''' Pickle object members, prioritizing custom encoding if present. '''
if hasattr(root, "__pickle_to_xml__"):
attributesToPickle = root.__pickle_to_xml__
else:
# avoid members which are python internal
attributesToPickle = [ name for name in dir(root) if not name.startswith("__") ]
for name in attributesToPickle:
obj = getattr(root, name)
# do not pickle member functions
if self._isCallable(obj): continue
# is there some special encoding method ??
if hasattr(root, f"_xml_encode_{name}"):
value = getattr(root, f"_xml_encode_{name}")()
element = fabric.createElement(name)
element.attributes["type"]='custom'
element.appendChild(fabric.createTextNode(value))
node.appendChild(element)
else:
node.appendChild(self.pickle(obj, fabric, name))
return node
#
# unpickling
#
# helper functions
def _getElementChilds(self, node, doLower = 1):
""" returns list of (tagname, element) for all element childs of node """
dolow = doLower and (lambda x:x.lower()) or (lambda x:x)
return [ (dolow(no.tagName), no) for no in node.childNodes if no.nodeType != no.TEXT_NODE ]
def _getText(self, nodelist):
""" returns collected and stripped text of textnodes among nodes in nodelist """
rc = ""
for node in nodelist:
if node.nodeType == node.TEXT_NODE:
rc = rc + node.data
return rc.strip()
[docs]
def unpickle(self, node, obj=None):
''' main unpickle function '''
typeName= node.attributes["type"].value
if typeName in self._easyToPickle:
initValue = self._getText(node.childNodes)
types = {'int': int, 'float': float, 'str': str, 'unicode': str, 'long': int}
return types[typeName](initValue)
elif typeName=="tuple":
return self._unpickleTuple(node)
elif typeName=="list":
return self._unpickleList(node)
elif typeName=="dict":
return self._unpickleDict(node)
elif typeName=="custom":
fun = f"_xml_decode_{node.tagName}"
if not obj or not hasattr(obj, fun): return ''
return getattr(obj, fun)(self._getText(node.childNodes))
else:
try:
import __main__
obj = getattr(__main__, typeName)()
except Exception:
obj = self.getMod(typeName)
for name, element in self._getElementChilds(node):
obj.__dict__[name] = self.unpickle(element, obj)
return obj
def _unpickleList(self, node):
li = []
# collect entries, you can not assume that the
# members of the list appear in the right order !
for name, element in self._getElementChilds(node):
if name != "item":
raise xmlunpicklingException()
idx = int(element.attributes["index"].value)
obj = self.unpickle(element)
li.append((idx, obj))
# rebuild list with right order
li.sort(key=lambda x: x[0])
return [ item[1] for item in li ]
def _unpickleTuple(self, node):
return tuple(self._unpickleList(node))
def _unpickleDict(self, node):
dd = dict()
for name, element in self._getElementChilds(node):
if name != "item":
raise xmlunpicklingException()
childList = self._getElementChilds(element)
if len(childList) != 2:
raise xmlunpicklingException()
for name, element in childList:
if name=="key":
key = self.unpickle(element)
elif name=="value":
value = self.unpickle(element)
dd[key]=value
return dd