123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- from __future__ import absolute_import
- # Copyright (c) 2010-2019 openpyxl
- from copy import copy
- from keyword import kwlist
- KEYWORDS = frozenset(kwlist)
- from . import Descriptor
- from . import _Serialiasable
- from .sequence import (
- Sequence,
- NestedSequence,
- MultiSequencePart,
- )
- 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
- """
- # 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(desc.store, [])
- attrib[desc.store].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)
- 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
|