Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/openpyxl/worksheet/header_footer.py: 37%

109 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1# Copyright (c) 2010-2022 openpyxl 

2 

3# Simplified implementation of headers and footers: let worksheets have separate items 

4 

5import re 

6from warnings import warn 

7 

8from openpyxl.descriptors import ( 

9 Alias, 

10 Bool, 

11 Strict, 

12 String, 

13 Integer, 

14 MatchPattern, 

15 Typed, 

16) 

17from openpyxl.descriptors.serialisable import Serialisable 

18 

19 

20from openpyxl.xml.functions import Element 

21from openpyxl.utils.escape import escape, unescape 

22 

23 

24FONT_PATTERN = '&"(?P<font>.+)"' 

25COLOR_PATTERN = "&K(?P<color>[A-F0-9]{6})" 

26SIZE_REGEX = r"&(?P<size>\d+\s?)" 

27FORMAT_REGEX = re.compile("{0}|{1}|{2}".format(FONT_PATTERN, COLOR_PATTERN, 

28 SIZE_REGEX) 

29 ) 

30 

31def _split_string(text): 

32 """ 

33 Split the combined (decoded) string into left, center and right parts 

34 

35 # See http://stackoverflow.com/questions/27711175/regex-with-multiple-optional-groups for discussion 

36 """ 

37 

38 ITEM_REGEX = re.compile(""" 

39 (&L(?P<left>.+?))? 

40 (&C(?P<center>.+?))? 

41 (&R(?P<right>.+?))? 

42 $""", re.VERBOSE | re.DOTALL) 

43 

44 m = ITEM_REGEX.match(text) 

45 try: 

46 parts = m.groupdict() 

47 except AttributeError: 

48 warn("""Cannot parse header or footer so it will be ignored""") 

49 parts = {'left':'', 'right':'', 'center':''} 

50 return parts 

51 

52 

53class _HeaderFooterPart(Strict): 

54 

55 """ 

56 Individual left/center/right header/footer part 

57 

58 Do not use directly. 

59 

60 Header & Footer ampersand codes: 

61 

62 * &A Inserts the worksheet name 

63 * &B Toggles bold 

64 * &D or &[Date] Inserts the current date 

65 * &E Toggles double-underline 

66 * &F or &[File] Inserts the workbook name 

67 * &I Toggles italic 

68 * &N or &[Pages] Inserts the total page count 

69 * &S Toggles strikethrough 

70 * &T Inserts the current time 

71 * &[Tab] Inserts the worksheet name 

72 * &U Toggles underline 

73 * &X Toggles superscript 

74 * &Y Toggles subscript 

75 * &P or &[Page] Inserts the current page number 

76 * &P+n Inserts the page number incremented by n 

77 * &P-n Inserts the page number decremented by n 

78 * &[Path] Inserts the workbook path 

79 * && Escapes the ampersand character 

80 * &"fontname" Selects the named font 

81 * &nn Selects the specified 2-digit font point size 

82 

83 Colours are in RGB Hex 

84 """ 

85 

86 text = String(allow_none=True) 

87 font = String(allow_none=True) 

88 size = Integer(allow_none=True) 

89 RGB = ("^[A-Fa-f0-9]{6}$") 

90 color = MatchPattern(allow_none=True, pattern=RGB) 

91 

92 

93 def __init__(self, text=None, font=None, size=None, color=None): 

94 self.text = text 

95 self.font = font 

96 self.size = size 

97 self.color = color 

98 

99 

100 def __str__(self): 

101 """ 

102 Convert to Excel HeaderFooter miniformat minus position 

103 """ 

104 fmt = [] 

105 if self.font: 

106 fmt.append(u'&"{0}"'.format(self.font)) 

107 if self.size: 

108 fmt.append("&{0} ".format(self.size)) 

109 if self.color: 

110 fmt.append("&K{0}".format(self.color)) 

111 return u"".join(fmt + [self.text]) 

112 

113 def __bool__(self): 

114 return bool(self.text) 

115 

116 

117 

118 @classmethod 

119 def from_str(cls, text): 

120 """ 

121 Convert from miniformat to object 

122 """ 

123 keys = ('font', 'color', 'size') 

124 kw = dict((k, v) for match in FORMAT_REGEX.findall(text) 

125 for k, v in zip(keys, match) if v) 

126 

127 kw['text'] = FORMAT_REGEX.sub('', text) 

128 

129 return cls(**kw) 

130 

131 

132class HeaderFooterItem(Strict): 

133 """ 

134 Header or footer item 

135 

136 """ 

137 

138 left = Typed(expected_type=_HeaderFooterPart) 

139 center = Typed(expected_type=_HeaderFooterPart) 

140 centre = Alias("center") 

141 right = Typed(expected_type=_HeaderFooterPart) 

142 

143 __keys = ('L', 'C', 'R') 

144 

145 

146 def __init__(self, left=None, right=None, center=None): 

147 if left is None: 

148 left = _HeaderFooterPart() 

149 self.left = left 

150 if center is None: 

151 center = _HeaderFooterPart() 

152 self.center = center 

153 if right is None: 

154 right = _HeaderFooterPart() 

155 self.right = right 

156 

157 

158 def __str__(self): 

159 """ 

160 Pack parts into a single string 

161 """ 

162 TRANSFORM = {'&[Tab]': '&A', '&[Pages]': '&N', '&[Date]': '&D', 

163 '&[Path]': '&Z', '&[Page]': '&P', '&[Time]': '&T', '&[File]': '&F', 

164 '&[Picture]': '&G'} 

165 

166 # escape keys and create regex 

167 SUBS_REGEX = re.compile("|".join(["({0})".format(re.escape(k)) 

168 for k in TRANSFORM])) 

169 

170 def replace(match): 

171 """ 

172 Callback for re.sub 

173 Replace expanded control with mini-format equivalent 

174 """ 

175 sub = match.group(0) 

176 return TRANSFORM[sub] 

177 

178 txt = [] 

179 for key, part in zip( 

180 self.__keys, [self.left, self.center, self.right]): 

181 if part.text is not None: 

182 txt.append(u"&{0}{1}".format(key, str(part))) 

183 txt = "".join(txt) 

184 txt = SUBS_REGEX.sub(replace, txt) 

185 return escape(txt) 

186 

187 

188 def __bool__(self): 

189 return any([self.left, self.center, self.right]) 

190 

191 

192 

193 def to_tree(self, tagname): 

194 """ 

195 Return as XML node 

196 """ 

197 el = Element(tagname) 

198 el.text = str(self) 

199 return el 

200 

201 

202 @classmethod 

203 def from_tree(cls, node): 

204 if node.text: 

205 text = unescape(node.text) 

206 parts = _split_string(text) 

207 for k, v in parts.items(): 

208 if v is not None: 

209 parts[k] = _HeaderFooterPart.from_str(v) 

210 self = cls(**parts) 

211 return self 

212 

213 

214class HeaderFooter(Serialisable): 

215 

216 tagname = "headerFooter" 

217 

218 differentOddEven = Bool(allow_none=True) 

219 differentFirst = Bool(allow_none=True) 

220 scaleWithDoc = Bool(allow_none=True) 

221 alignWithMargins = Bool(allow_none=True) 

222 oddHeader = Typed(expected_type=HeaderFooterItem, allow_none=True) 

223 oddFooter = Typed(expected_type=HeaderFooterItem, allow_none=True) 

224 evenHeader = Typed(expected_type=HeaderFooterItem, allow_none=True) 

225 evenFooter = Typed(expected_type=HeaderFooterItem, allow_none=True) 

226 firstHeader = Typed(expected_type=HeaderFooterItem, allow_none=True) 

227 firstFooter = Typed(expected_type=HeaderFooterItem, allow_none=True) 

228 

229 __elements__ = ("oddHeader", "oddFooter", "evenHeader", "evenFooter", "firstHeader", "firstFooter") 

230 

231 def __init__(self, 

232 differentOddEven=None, 

233 differentFirst=None, 

234 scaleWithDoc=None, 

235 alignWithMargins=None, 

236 oddHeader=None, 

237 oddFooter=None, 

238 evenHeader=None, 

239 evenFooter=None, 

240 firstHeader=None, 

241 firstFooter=None, 

242 ): 

243 self.differentOddEven = differentOddEven 

244 self.differentFirst = differentFirst 

245 self.scaleWithDoc = scaleWithDoc 

246 self.alignWithMargins = alignWithMargins 

247 if oddHeader is None: 

248 oddHeader = HeaderFooterItem() 

249 self.oddHeader = oddHeader 

250 if oddFooter is None: 

251 oddFooter = HeaderFooterItem() 

252 self.oddFooter = oddFooter 

253 if evenHeader is None: 

254 evenHeader = HeaderFooterItem() 

255 self.evenHeader = evenHeader 

256 if evenFooter is None: 

257 evenFooter = HeaderFooterItem() 

258 self.evenFooter = evenFooter 

259 if firstHeader is None: 

260 firstHeader = HeaderFooterItem() 

261 self.firstHeader = firstHeader 

262 if firstFooter is None: 

263 firstFooter = HeaderFooterItem() 

264 self.firstFooter = firstFooter 

265 

266 

267 def __bool__(self): 

268 parts = [getattr(self, attr) for attr in self.__attrs__ + self.__elements__] 

269 return any(parts) 

270