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

1# Copyright (c) 2010-2023 openpyxl 

2 

3"""Implementation of custom properties see § 22.3 in the specification""" 

4 

5 

6from warnings import warn 

7 

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) 

22 

23from openpyxl.xml.constants import ( 

24 CUSTPROPS_NS, 

25 VTYPES_NS, 

26 CPROPS_FMTID, 

27) 

28 

29from .core import NestedDateTime 

30 

31 

32class NestedBoolText(Bool, NestedText): 

33 """ 

34 Descriptor for handling nested elements with the value stored in the text part 

35 """ 

36 

37 pass 

38 

39 

40class _CustomDocumentProperty(Serialisable): 

41 

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 """ 

47 

48 tagname = "property" 

49 _typ = None 

50 

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() 

60 

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 

72 

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) 

79 

80 

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" 

90 

91 

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, "") 

96 

97 return super().to_tree(tagname=None, idx=None, namespace=None) 

98 

99 

100class _CustomDocumentPropertyList(Serialisable): 

101 

102 """ 

103 Parses and seriliases property lists but is not used directly 

104 """ 

105 

106 tagname = "Properties" 

107 

108 property = Sequence(expected_type=_CustomDocumentProperty, namespace=CUSTPROPS_NS) 

109 customProps = Alias("property") 

110 

111 

112 def __init__(self, property=()): 

113 self.property = property 

114 

115 

116 def __len__(self): 

117 return len(self.property) 

118 

119 

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) 

125 

126 return tree 

127 

128 

129class _TypedProperty(Strict): 

130 

131 name = String() 

132 

133 def __init__(self, 

134 name, 

135 value): 

136 self.name = name 

137 self.value = value 

138 

139 

140 def __eq__(self, other): 

141 return self.name == other.name and self.value == other.value 

142 

143 

144 def __repr__(self): 

145 return f"{self.__class__.__name__}, name={self.name}, value={self.value}" 

146 

147 

148class IntProperty(_TypedProperty): 

149 

150 value = Integer() 

151 

152 

153class FloatProperty(_TypedProperty): 

154 

155 value = Float() 

156 

157 

158class StringProperty(_TypedProperty): 

159 

160 value = String(allow_none=True) 

161 

162 

163class DateTimeProperty(_TypedProperty): 

164 

165 value = DateTime() 

166 

167 

168class BoolProperty(_TypedProperty): 

169 

170 value = Bool() 

171 

172 

173class LinkProperty(_TypedProperty): 

174 

175 value = String() 

176 

177 

178# from Python 

179CLASS_MAPPING = { 

180 StringProperty: "lpwstr", 

181 IntProperty: "i4", 

182 FloatProperty: "r8", 

183 DateTimeProperty: "filetime", 

184 BoolProperty: "bool", 

185 LinkProperty: "linkTarget" 

186} 

187 

188XML_MAPPING = {v:k for k,v in CLASS_MAPPING.items()} 

189 

190 

191class CustomPropertyList(Strict): 

192 

193 

194 props = Sequence(expected_type=_TypedProperty) 

195 

196 def __init__(self): 

197 self.props = [] 

198 

199 

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 

209 

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 

219 

220 new_prop = typ(name=prop.name, value=value) 

221 new_props.append(new_prop) 

222 return new_props 

223 

224 

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 

231 

232 

233 def to_tree(self): 

234 props = [] 

235 

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) 

245 

246 prop_list = _CustomDocumentPropertyList(property=props) 

247 return prop_list.to_tree() 

248 

249 

250 def __len__(self): 

251 return len(self.props) 

252 

253 

254 @property 

255 def names(self): 

256 """List of property names""" 

257 return [p.name for p in self.props] 

258 

259 

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") 

268 

269 

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") 

279 

280 

281 def __repr__(self): 

282 return f"{self.__class__.__name__} containing {self.props}" 

283 

284 

285 def __iter__(self): 

286 return iter(self.props)