from __future__ import absolute_import # copyright openpyxl 2010-2015 from keyword import kwlist KEYWORDS = frozenset(kwlist) from . import _Serialiasable from .sequence import Sequence, NestedSequence from .namespace import namespaced from openpyxl.compat import safe_string from openpyxl.xml.functions import ( Element, localname, ) seq_types = (list, tuple) class Serialisable(_Serialiasable): """ Objects can serialise to XML their attributes and child objects. The following class attributes are created by the metaclass at runtime: __attrs__ = attributes __nested__ = single-valued child treated as an attribute __elements__ = child elements """ __attrs__ = None __nested__ = None __elements__ = None __namespaced__ = None idx_base = 0 @property def tagname(self): raise(NotImplementedError) namespace = None @classmethod def from_tree(cls, node): """ Create object from XML """ attrib = dict(node.attrib) for key, ns in cls.__namespaced__: if ns in attrib: attrib[key] = attrib[ns] del attrib[ns] for el in node: tag = localname(el) if tag in KEYWORDS: tag = "_" + tag desc = getattr(cls, tag, None) if desc is None or isinstance(desc, property): continue if hasattr(desc, 'from_tree'): #descriptor manages conversion obj = desc.from_tree(el) else: if hasattr(desc.expected_type, "from_tree"): #complex type obj = desc.expected_type.from_tree(el) else: #primitive obj = el.text if isinstance(desc, NestedSequence): attrib[tag] = obj elif isinstance(desc, Sequence): attrib.setdefault(tag, []) attrib[tag].append(obj) else: attrib[tag] = obj return cls(**attrib) def to_tree(self, tagname=None, idx=None, namespace=None): if tagname is None: tagname = self.tagname # keywords have to be masked if tagname.startswith("_"): tagname = tagname[1:] tagname = namespaced(self, tagname, namespace) namespace = getattr(self, "namespace", namespace) attrs = dict(self) for key, ns in self.__namespaced__: if key in attrs: attrs[ns] = attrs[key] del attrs[key] el = Element(tagname, attrs) for child_tag in self.__elements__: desc = getattr(self.__class__, child_tag, None) obj = getattr(self, child_tag) if isinstance(obj, seq_types): if isinstance(desc, NestedSequence): # wrap sequence in container if not obj: continue nodes = [desc.to_tree(child_tag, obj, namespace)] elif isinstance(desc, Sequence): # sequence desc.idx_base = self.idx_base nodes = (desc.to_tree(child_tag, obj, namespace)) else: # property nodes = (v.to_tree(child_tag, namespace) for v in obj) for node in nodes: el.append(node) else: if child_tag in self.__nested__: node = desc.to_tree(child_tag, obj, namespace) elif obj is None: continue else: node = obj.to_tree(child_tag) if node is not None: el.append(node) return el def __iter__(self): for attr in self.__attrs__: value = getattr(self, attr) if value is not None: yield attr, safe_string(value) def __eq__(self, other): if not dict(self) == dict(other): return False for el in self.__elements__: if getattr(self, el) != getattr(other, el): return False return True def __ne__(self, other): return not self == other