Source code for openpyxl.descriptors.serialisable

# Copyright (c) 2010-2023 openpyxl

from copy import copy
from keyword import kwlist
KEYWORDS = frozenset(kwlist)

from . import Descriptor
from . import MetaSerialisable
from .sequence import (
from .namespace import namespaced

from openpyxl.compat import safe_string
from openpyxl.xml.functions import (

seq_types = (list, tuple)

[docs]class Serialisable(metaclass=MetaSerialisable): """ 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
[docs] @classmethod def from_tree(cls, node): """ Create object from XML """ # strip known namespaces from attributes attrib = dict(node.attrib) for key, ns in cls.__namespaced__: if ns in attrib: attrib[key] = attrib[ns] del attrib[ns] # strip attributes with unknown namespaces for key in list(attrib): if key.startswith('{'): del attrib[key] elif key in KEYWORDS: attrib["_" + key] = attrib[key] del attrib[key] elif "-" in key: n = key.replace("-", "_") attrib[n] = attrib[key] del attrib[key] if node.text and "attr_text" in cls.__attrs__: attrib["attr_text"] = node.text 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) elif isinstance(desc, MultiSequencePart): attrib.setdefault(, []) attrib[].append(obj) else: attrib[tag] = obj return cls(**attrib)
[docs] 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) if "attr_text" in self.__attrs__: el.text = safe_string(getattr(self, "attr_text")) for child_tag in self.__elements__: desc = getattr(self.__class__, child_tag, None) obj = getattr(self, child_tag) if hasattr(desc, "namespace") and hasattr(obj, 'namespace'): obj.namespace = desc.namespace 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 attr.startswith("_"): attr = attr[1:] elif attr != "attr_text" and "_" in attr: desc = getattr(self.__class__, attr) if getattr(desc, "hyphenated", False): attr = attr.replace("_", "-") if attr != "attr_text" and value is not None: yield attr, safe_string(value) def __eq__(self, other): if not self.__class__ == other.__class__: return False elif 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 def __repr__(self): s = u"<{0}.{1} object>\nParameters:".format( self.__module__, self.__class__.__name__ ) args = [] for k in self.__attrs__ + self.__elements__: v = getattr(self, k) if isinstance(v, Descriptor): v = None args.append(u"{0}={1}".format(k, repr(v))) args = u", ".join(args) return u"\n".join([s, args]) def __hash__(self): fields = [] for attr in self.__attrs__ + self.__elements__: val = getattr(self, attr) if isinstance(val, list): val = tuple(val) fields.append(val) return hash(tuple(fields)) def __add__(self, other): if type(self) != type(other): raise TypeError("Cannot combine instances of different types") vals = {} for attr in self.__attrs__: vals[attr] = getattr(self, attr) or getattr(other, attr) for el in self.__elements__: a = getattr(self, el) b = getattr(other, el) if a and b: vals[el] = a + b else: vals[el] = a or b return self.__class__(**vals) def __copy__(self): # serialise to xml and back to avoid shallow copies xml = self.to_tree(tagname="dummy") cp = self.__class__.from_tree(xml) # copy any non-persisted attributed for k in self.__dict__: if k not in self.__attrs__ + self.__elements__: v = copy(getattr(self, k)) setattr(cp, k, v) return cp