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
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1# Copyright (c) 2010-2022 openpyxl
3# Simplified implementation of headers and footers: let worksheets have separate items
5import re
6from warnings import warn
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
20from openpyxl.xml.functions import Element
21from openpyxl.utils.escape import escape, unescape
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 )
31def _split_string(text):
32 """
33 Split the combined (decoded) string into left, center and right parts
35 # See http://stackoverflow.com/questions/27711175/regex-with-multiple-optional-groups for discussion
36 """
38 ITEM_REGEX = re.compile("""
39 (&L(?P<left>.+?))?
40 (&C(?P<center>.+?))?
41 (&R(?P<right>.+?))?
42 $""", re.VERBOSE | re.DOTALL)
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
53class _HeaderFooterPart(Strict):
55 """
56 Individual left/center/right header/footer part
58 Do not use directly.
60 Header & Footer ampersand codes:
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
83 Colours are in RGB Hex
84 """
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)
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
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])
113 def __bool__(self):
114 return bool(self.text)
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)
127 kw['text'] = FORMAT_REGEX.sub('', text)
129 return cls(**kw)
132class HeaderFooterItem(Strict):
133 """
134 Header or footer item
136 """
138 left = Typed(expected_type=_HeaderFooterPart)
139 center = Typed(expected_type=_HeaderFooterPart)
140 centre = Alias("center")
141 right = Typed(expected_type=_HeaderFooterPart)
143 __keys = ('L', 'C', 'R')
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
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'}
166 # escape keys and create regex
167 SUBS_REGEX = re.compile("|".join(["({0})".format(re.escape(k))
168 for k in TRANSFORM]))
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]
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)
188 def __bool__(self):
189 return any([self.left, self.center, self.right])
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
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
214class HeaderFooter(Serialisable):
216 tagname = "headerFooter"
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)
229 __elements__ = ("oddHeader", "oddFooter", "evenHeader", "evenFooter", "firstHeader", "firstFooter")
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
267 def __bool__(self):
268 parts = [getattr(self, attr) for attr in self.__attrs__ + self.__elements__]
269 return any(parts)