serialisable.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. from __future__ import absolute_import
  2. # Copyright (c) 2010-2019 openpyxl
  3. from copy import copy
  4. from keyword import kwlist
  5. KEYWORDS = frozenset(kwlist)
  6. from . import Descriptor
  7. from . import _Serialiasable
  8. from .sequence import (
  9. Sequence,
  10. NestedSequence,
  11. MultiSequencePart,
  12. )
  13. from .namespace import namespaced
  14. from openpyxl.compat import safe_string
  15. from openpyxl.xml.functions import (
  16. Element,
  17. localname,
  18. )
  19. seq_types = (list, tuple)
  20. class Serialisable(_Serialiasable):
  21. """
  22. Objects can serialise to XML their attributes and child objects.
  23. The following class attributes are created by the metaclass at runtime:
  24. __attrs__ = attributes
  25. __nested__ = single-valued child treated as an attribute
  26. __elements__ = child elements
  27. """
  28. __attrs__ = None
  29. __nested__ = None
  30. __elements__ = None
  31. __namespaced__ = None
  32. idx_base = 0
  33. @property
  34. def tagname(self):
  35. raise(NotImplementedError)
  36. namespace = None
  37. @classmethod
  38. def from_tree(cls, node):
  39. """
  40. Create object from XML
  41. """
  42. # strip known namespaces from attributes
  43. attrib = dict(node.attrib)
  44. for key, ns in cls.__namespaced__:
  45. if ns in attrib:
  46. attrib[key] = attrib[ns]
  47. del attrib[ns]
  48. # strip attributes with unknown namespaces
  49. for key in list(attrib):
  50. if key.startswith('{'):
  51. del attrib[key]
  52. elif key in KEYWORDS:
  53. attrib["_" + key] = attrib[key]
  54. del attrib[key]
  55. elif "-" in key:
  56. n = key.replace("-", "_")
  57. attrib[n] = attrib[key]
  58. del attrib[key]
  59. if node.text and "attr_text" in cls.__attrs__:
  60. attrib["attr_text"] = node.text
  61. for el in node:
  62. tag = localname(el)
  63. if tag in KEYWORDS:
  64. tag = "_" + tag
  65. desc = getattr(cls, tag, None)
  66. if desc is None or isinstance(desc, property):
  67. continue
  68. if hasattr(desc, 'from_tree'):
  69. #descriptor manages conversion
  70. obj = desc.from_tree(el)
  71. else:
  72. if hasattr(desc.expected_type, "from_tree"):
  73. #complex type
  74. obj = desc.expected_type.from_tree(el)
  75. else:
  76. #primitive
  77. obj = el.text
  78. if isinstance(desc, NestedSequence):
  79. attrib[tag] = obj
  80. elif isinstance(desc, Sequence):
  81. attrib.setdefault(tag, [])
  82. attrib[tag].append(obj)
  83. elif isinstance(desc, MultiSequencePart):
  84. attrib.setdefault(desc.store, [])
  85. attrib[desc.store].append(obj)
  86. else:
  87. attrib[tag] = obj
  88. return cls(**attrib)
  89. def to_tree(self, tagname=None, idx=None, namespace=None):
  90. if tagname is None:
  91. tagname = self.tagname
  92. # keywords have to be masked
  93. if tagname.startswith("_"):
  94. tagname = tagname[1:]
  95. tagname = namespaced(self, tagname, namespace)
  96. namespace = getattr(self, "namespace", namespace)
  97. attrs = dict(self)
  98. for key, ns in self.__namespaced__:
  99. if key in attrs:
  100. attrs[ns] = attrs[key]
  101. del attrs[key]
  102. el = Element(tagname, attrs)
  103. if "attr_text" in self.__attrs__:
  104. el.text = safe_string(getattr(self, "attr_text"))
  105. for child_tag in self.__elements__:
  106. desc = getattr(self.__class__, child_tag, None)
  107. obj = getattr(self, child_tag)
  108. if hasattr(desc, "namespace") and hasattr(obj, 'namespace'):
  109. obj.namespace = desc.namespace
  110. if isinstance(obj, seq_types):
  111. if isinstance(desc, NestedSequence):
  112. # wrap sequence in container
  113. if not obj:
  114. continue
  115. nodes = [desc.to_tree(child_tag, obj, namespace)]
  116. elif isinstance(desc, Sequence):
  117. # sequence
  118. desc.idx_base = self.idx_base
  119. nodes = (desc.to_tree(child_tag, obj, namespace))
  120. else: # property
  121. nodes = (v.to_tree(child_tag, namespace) for v in obj)
  122. for node in nodes:
  123. el.append(node)
  124. else:
  125. if child_tag in self.__nested__:
  126. node = desc.to_tree(child_tag, obj, namespace)
  127. elif obj is None:
  128. continue
  129. else:
  130. node = obj.to_tree(child_tag)
  131. if node is not None:
  132. el.append(node)
  133. return el
  134. def __iter__(self):
  135. for attr in self.__attrs__:
  136. value = getattr(self, attr)
  137. if attr.startswith("_"):
  138. attr = attr[1:]
  139. elif attr != "attr_text" and "_" in attr:
  140. desc = getattr(self.__class__, attr)
  141. if getattr(desc, "hyphenated", False):
  142. attr = attr.replace("_", "-")
  143. if attr != "attr_text" and value is not None:
  144. yield attr, safe_string(value)
  145. def __eq__(self, other):
  146. if not self.__class__ == other.__class__:
  147. return False
  148. elif not dict(self) == dict(other):
  149. return False
  150. for el in self.__elements__:
  151. if getattr(self, el) != getattr(other, el):
  152. return False
  153. return True
  154. def __ne__(self, other):
  155. return not self == other
  156. def __repr__(self):
  157. s = u"<{0}.{1} object>\nParameters:".format(
  158. self.__module__,
  159. self.__class__.__name__
  160. )
  161. args = []
  162. for k in self.__attrs__ + self.__elements__:
  163. v = getattr(self, k)
  164. if isinstance(v, Descriptor):
  165. v = None
  166. args.append(u"{0}={1}".format(k, repr(v)))
  167. args = u", ".join(args)
  168. return u"\n".join([s, args])
  169. def __hash__(self):
  170. fields = []
  171. for attr in self.__attrs__ + self.__elements__:
  172. val = getattr(self, attr)
  173. if isinstance(val, list):
  174. val = tuple(val)
  175. fields.append(val)
  176. return hash(tuple(fields))
  177. def __add__(self, other):
  178. if type(self) != type(other):
  179. raise TypeError("Cannot combine instances of different types")
  180. vals = {}
  181. for attr in self.__attrs__:
  182. vals[attr] = getattr(self, attr) or getattr(other, attr)
  183. for el in self.__elements__:
  184. a = getattr(self, el)
  185. b = getattr(other, el)
  186. if a and b:
  187. vals[el] = a + b
  188. else:
  189. vals[el] = a or b
  190. return self.__class__(**vals)
  191. def __copy__(self):
  192. # serialise to xml and back to avoid shallow copies
  193. xml = self.to_tree(tagname="dummy")
  194. cp = self.__class__.from_tree(xml)
  195. # copy any non-persisted attributed
  196. for k in self.__dict__:
  197. if k not in self.__attrs__ + self.__elements__:
  198. v = copy(getattr(self, k))
  199. setattr(cp, k, v)
  200. return cp