Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/openpyxl/packaging/custom.py: 47%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

146 statements  

1# Copyright (c) 2010-2024 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 props = [] 

207 

208 for prop in prop_list.property: 

209 attr = prop.type 

210 

211 typ = XML_MAPPING.get(attr, None) 

212 if not typ: 

213 warn(f"Unknown type for {prop.name}") 

214 continue 

215 value = getattr(prop, attr) 

216 link = prop.linkTarget 

217 if link is not None: 

218 typ = LinkProperty 

219 value = prop.linkTarget 

220 

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

222 props.append(new_prop) 

223 

224 new_prop_list = cls() 

225 new_prop_list.props = props 

226 return new_prop_list 

227 

228 

229 def append(self, prop): 

230 if prop.name in self.names: 

231 raise ValueError(f"Property with name {prop.name} already exists") 

232 

233 self.props.append(prop) 

234 

235 

236 def to_tree(self): 

237 props = [] 

238 

239 for p in self.props: 

240 attr = CLASS_MAPPING.get(p.__class__, None) 

241 if not attr: 

242 raise TypeError("Unknown adapter for {p}") 

243 np = _CustomDocumentProperty(name=p.name, **{attr:p.value}) 

244 if isinstance(p, LinkProperty): 

245 np._typ = "lpwstr" 

246 #np.lpwstr = "" 

247 props.append(np) 

248 

249 prop_list = _CustomDocumentPropertyList(property=props) 

250 return prop_list.to_tree() 

251 

252 

253 def __len__(self): 

254 return len(self.props) 

255 

256 

257 @property 

258 def names(self): 

259 """List of property names""" 

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

261 

262 

263 def __getitem__(self, name): 

264 """ 

265 Get property by name 

266 """ 

267 for p in self.props: 

268 if p.name == name: 

269 return p 

270 raise KeyError(f"Property with name {name} not found") 

271 

272 

273 def __delitem__(self, name): 

274 """ 

275 Delete a propery by name 

276 """ 

277 for idx, p in enumerate(self.props): 

278 if p.name == name: 

279 self.props.pop(idx) 

280 return 

281 raise KeyError(f"Property with name {name} not found") 

282 

283 

284 def __repr__(self): 

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

286 

287 

288 def __iter__(self): 

289 return iter(self.props)