Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/openpyxl/packaging/custom.py: 47%
146 statements
« prev ^ index » next coverage.py v7.3.3, created at 2023-12-20 06:34 +0000
« prev ^ index » next coverage.py v7.3.3, created at 2023-12-20 06:34 +0000
1# Copyright (c) 2010-2023 openpyxl
3"""Implementation of custom properties see § 22.3 in the specification"""
6from warnings import warn
8from openpyxl.descriptors import Strict
9from openpyxl.descriptors.serialisable import Serialisable
10from openpyxl.descriptors.sequence import Sequence
11from openpyxl.descriptors import (
12 Alias,
13 String,
14 Integer,
15 Float,
16 DateTime,
17 Bool,
18)
19from openpyxl.descriptors.nested import (
20 NestedText,
21)
23from openpyxl.xml.constants import (
24 CUSTPROPS_NS,
25 VTYPES_NS,
26 CPROPS_FMTID,
27)
29from .core import NestedDateTime
32class NestedBoolText(Bool, NestedText):
33 """
34 Descriptor for handling nested elements with the value stored in the text part
35 """
37 pass
40class _CustomDocumentProperty(Serialisable):
42 """
43 Low-level representation of a Custom Document Property.
44 Not used directly
45 Must always contain a child element, even if this is empty
46 """
48 tagname = "property"
49 _typ = None
51 name = String(allow_none=True)
52 lpwstr = NestedText(expected_type=str, allow_none=True, namespace=VTYPES_NS)
53 i4 = NestedText(expected_type=int, allow_none=True, namespace=VTYPES_NS)
54 r8 = NestedText(expected_type=float, allow_none=True, namespace=VTYPES_NS)
55 filetime = NestedDateTime(allow_none=True, namespace=VTYPES_NS)
56 bool = NestedBoolText(expected_type=bool, allow_none=True, namespace=VTYPES_NS)
57 linkTarget = String(expected_type=str, allow_none=True)
58 fmtid = String()
59 pid = Integer()
61 def __init__(self,
62 name=None,
63 pid=0,
64 fmtid=CPROPS_FMTID,
65 linkTarget=None,
66 **kw):
67 self.fmtid = fmtid
68 self.pid = pid
69 self.name = name
70 self._typ = None
71 self.linkTarget = linkTarget
73 for k, v in kw.items():
74 setattr(self, k, v)
75 setattr(self, "_typ", k) # ugh!
76 for e in self.__elements__:
77 if e not in kw:
78 setattr(self, e, None)
81 @property
82 def type(self):
83 if self._typ is not None:
84 return self._typ
85 for a in self.__elements__:
86 if getattr(self, a) is not None:
87 return a
88 if self.linkTarget is not None:
89 return "linkTarget"
92 def to_tree(self, tagname=None, idx=None, namespace=None):
93 child = getattr(self, self._typ, None)
94 if child is None:
95 setattr(self, self._typ, "")
97 return super().to_tree(tagname=None, idx=None, namespace=None)
100class _CustomDocumentPropertyList(Serialisable):
102 """
103 Parses and seriliases property lists but is not used directly
104 """
106 tagname = "Properties"
108 property = Sequence(expected_type=_CustomDocumentProperty, namespace=CUSTPROPS_NS)
109 customProps = Alias("property")
112 def __init__(self, property=()):
113 self.property = property
116 def __len__(self):
117 return len(self.property)
120 def to_tree(self, tagname=None, idx=None, namespace=None):
121 for idx, p in enumerate(self.property, 2):
122 p.pid = idx
123 tree = super().to_tree(tagname, idx, namespace)
124 tree.set("xmlns", CUSTPROPS_NS)
126 return tree
129class _TypedProperty(Strict):
131 name = String()
133 def __init__(self,
134 name,
135 value):
136 self.name = name
137 self.value = value
140 def __eq__(self, other):
141 return self.name == other.name and self.value == other.value
144 def __repr__(self):
145 return f"{self.__class__.__name__}, name={self.name}, value={self.value}"
148class IntProperty(_TypedProperty):
150 value = Integer()
153class FloatProperty(_TypedProperty):
155 value = Float()
158class StringProperty(_TypedProperty):
160 value = String(allow_none=True)
163class DateTimeProperty(_TypedProperty):
165 value = DateTime()
168class BoolProperty(_TypedProperty):
170 value = Bool()
173class LinkProperty(_TypedProperty):
175 value = String()
178# from Python
179CLASS_MAPPING = {
180 StringProperty: "lpwstr",
181 IntProperty: "i4",
182 FloatProperty: "r8",
183 DateTimeProperty: "filetime",
184 BoolProperty: "bool",
185 LinkProperty: "linkTarget"
186}
188XML_MAPPING = {v:k for k,v in CLASS_MAPPING.items()}
191class CustomPropertyList(Strict):
194 props = Sequence(expected_type=_TypedProperty)
196 def __init__(self):
197 self.props = []
200 @classmethod
201 def from_tree(cls, tree):
202 """
203 Create list from OOXML element
204 """
205 prop_list = _CustomDocumentPropertyList.from_tree(tree)
206 new_props = cls()
207 for prop in prop_list.property:
208 attr = prop.type
210 typ = XML_MAPPING.get(attr, None)
211 if not typ:
212 warn(f"Unknown type for {prop.name}")
213 continue
214 value = getattr(prop, attr)
215 link = prop.linkTarget
216 if link is not None:
217 typ = LinkProperty
218 value = prop.linkTarget
220 new_prop = typ(name=prop.name, value=value)
221 new_props.append(new_prop)
222 return new_props
225 def append(self, prop):
226 if prop.name in self.names:
227 raise ValueError(f"Property with name {prop.name} already exists")
228 props = self.props
229 props.append(prop)
230 self.props = props
233 def to_tree(self):
234 props = []
236 for p in self.props:
237 attr = CLASS_MAPPING.get(p.__class__, None)
238 if not attr:
239 raise TypeError("Unknown adapter for {p}")
240 np = _CustomDocumentProperty(name=p.name, **{attr:p.value})
241 if isinstance(p, LinkProperty):
242 np._typ = "lpwstr"
243 #np.lpwstr = ""
244 props.append(np)
246 prop_list = _CustomDocumentPropertyList(property=props)
247 return prop_list.to_tree()
250 def __len__(self):
251 return len(self.props)
254 @property
255 def names(self):
256 """List of property names"""
257 return [p.name for p in self.props]
260 def __getitem__(self, name):
261 """
262 Get property by name
263 """
264 for p in self.props:
265 if p.name == name:
266 return p
267 raise KeyError(f"Property with name {name} not found")
270 def __delitem__(self, name):
271 """
272 Delete a propery by name
273 """
274 for idx, p in enumerate(self.props):
275 if p.name == name:
276 self.props.pop(idx)
277 return
278 raise KeyError(f"Property with name {name} not found")
281 def __repr__(self):
282 return f"{self.__class__.__name__} containing {self.props}"
285 def __iter__(self):
286 return iter(self.props)