/tablib/packages/openpyxl/style.py
Python | 392 lines | 293 code | 57 blank | 42 comment | 7 complexity | f3216c8495818665db37bb778a7d4153 MD5 | raw file
1# file openpyxl/style.py
2
3# Copyright (c) 2010 openpyxl
4#
5# Permission is hereby granted, free of charge, to any person obtaining a copy
6# of this software and associated documentation files (the "Software"), to deal
7# in the Software without restriction, including without limitation the rights
8# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9# copies of the Software, and to permit persons to whom the Software is
10# furnished to do so, subject to the following conditions:
11#
12# The above copyright notice and this permission notice shall be included in
13# all copies or substantial portions of the Software.
14#
15# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21# THE SOFTWARE.
22#
23# @license: http://www.opensource.org/licenses/mit-license.php
24# @author: Eric Gazoni
25
26"""Style and formatting option tracking."""
27
28# Python stdlib imports
29import re
30try:
31 from hashlib import md5
32except ImportError:
33 from md5 import md5
34
35
36class HashableObject(object):
37 """Define how to hash property classes."""
38 __fields__ = None
39 __leaf__ = False
40
41 def __repr__(self):
42
43 return ':'.join([repr(getattr(self, x)) for x in self.__fields__])
44
45 def __hash__(self):
46
47# return int(md5(repr(self)).hexdigest(), 16)
48 return hash(repr(self))
49
50class Color(HashableObject):
51 """Named colors for use in styles."""
52 BLACK = 'FF000000'
53 WHITE = 'FFFFFFFF'
54 RED = 'FFFF0000'
55 DARKRED = 'FF800000'
56 BLUE = 'FF0000FF'
57 DARKBLUE = 'FF000080'
58 GREEN = 'FF00FF00'
59 DARKGREEN = 'FF008000'
60 YELLOW = 'FFFFFF00'
61 DARKYELLOW = 'FF808000'
62
63 __fields__ = ('index',)
64 __slots__ = __fields__
65 __leaf__ = True
66
67 def __init__(self, index):
68 super(Color, self).__init__()
69 self.index = index
70
71
72class Font(HashableObject):
73 """Font options used in styles."""
74 UNDERLINE_NONE = 'none'
75 UNDERLINE_DOUBLE = 'double'
76 UNDERLINE_DOUBLE_ACCOUNTING = 'doubleAccounting'
77 UNDERLINE_SINGLE = 'single'
78 UNDERLINE_SINGLE_ACCOUNTING = 'singleAccounting'
79
80 __fields__ = ('name',
81 'size',
82 'bold',
83 'italic',
84 'superscript',
85 'subscript',
86 'underline',
87 'strikethrough',
88 'color')
89 __slots__ = __fields__
90
91 def __init__(self):
92 super(Font, self).__init__()
93 self.name = 'Calibri'
94 self.size = 11
95 self.bold = False
96 self.italic = False
97 self.superscript = False
98 self.subscript = False
99 self.underline = self.UNDERLINE_NONE
100 self.strikethrough = False
101 self.color = Color(Color.BLACK)
102
103
104class Fill(HashableObject):
105 """Area fill patterns for use in styles."""
106 FILL_NONE = 'none'
107 FILL_SOLID = 'solid'
108 FILL_GRADIENT_LINEAR = 'linear'
109 FILL_GRADIENT_PATH = 'path'
110 FILL_PATTERN_DARKDOWN = 'darkDown'
111 FILL_PATTERN_DARKGRAY = 'darkGray'
112 FILL_PATTERN_DARKGRID = 'darkGrid'
113 FILL_PATTERN_DARKHORIZONTAL = 'darkHorizontal'
114 FILL_PATTERN_DARKTRELLIS = 'darkTrellis'
115 FILL_PATTERN_DARKUP = 'darkUp'
116 FILL_PATTERN_DARKVERTICAL = 'darkVertical'
117 FILL_PATTERN_GRAY0625 = 'gray0625'
118 FILL_PATTERN_GRAY125 = 'gray125'
119 FILL_PATTERN_LIGHTDOWN = 'lightDown'
120 FILL_PATTERN_LIGHTGRAY = 'lightGray'
121 FILL_PATTERN_LIGHTGRID = 'lightGrid'
122 FILL_PATTERN_LIGHTHORIZONTAL = 'lightHorizontal'
123 FILL_PATTERN_LIGHTTRELLIS = 'lightTrellis'
124 FILL_PATTERN_LIGHTUP = 'lightUp'
125 FILL_PATTERN_LIGHTVERTICAL = 'lightVertical'
126 FILL_PATTERN_MEDIUMGRAY = 'mediumGray'
127
128 __fields__ = ('fill_type',
129 'rotation',
130 'start_color',
131 'end_color')
132 __slots__ = __fields__
133
134 def __init__(self):
135 super(Fill, self).__init__()
136 self.fill_type = self.FILL_NONE
137 self.rotation = 0
138 self.start_color = Color(Color.WHITE)
139 self.end_color = Color(Color.BLACK)
140
141
142class Border(HashableObject):
143 """Border options for use in styles."""
144 BORDER_NONE = 'none'
145 BORDER_DASHDOT = 'dashDot'
146 BORDER_DASHDOTDOT = 'dashDotDot'
147 BORDER_DASHED = 'dashed'
148 BORDER_DOTTED = 'dotted'
149 BORDER_DOUBLE = 'double'
150 BORDER_HAIR = 'hair'
151 BORDER_MEDIUM = 'medium'
152 BORDER_MEDIUMDASHDOT = 'mediumDashDot'
153 BORDER_MEDIUMDASHDOTDOT = 'mediumDashDotDot'
154 BORDER_MEDIUMDASHED = 'mediumDashed'
155 BORDER_SLANTDASHDOT = 'slantDashDot'
156 BORDER_THICK = 'thick'
157 BORDER_THIN = 'thin'
158
159 __fields__ = ('border_style',
160 'color')
161 __slots__ = __fields__
162
163 def __init__(self):
164 super(Border, self).__init__()
165 self.border_style = self.BORDER_NONE
166 self.color = Color(Color.BLACK)
167
168
169class Borders(HashableObject):
170 """Border positioning for use in styles."""
171 DIAGONAL_NONE = 0
172 DIAGONAL_UP = 1
173 DIAGONAL_DOWN = 2
174 DIAGONAL_BOTH = 3
175
176 __fields__ = ('left',
177 'right',
178 'top',
179 'bottom',
180 'diagonal',
181 'diagonal_direction',
182 'all_borders',
183 'outline',
184 'inside',
185 'vertical',
186 'horizontal')
187 __slots__ = __fields__
188
189 def __init__(self):
190 super(Borders, self).__init__()
191 self.left = Border()
192 self.right = Border()
193 self.top = Border()
194 self.bottom = Border()
195 self.diagonal = Border()
196 self.diagonal_direction = self.DIAGONAL_NONE
197
198 self.all_borders = Border()
199 self.outline = Border()
200 self.inside = Border()
201 self.vertical = Border()
202 self.horizontal = Border()
203
204
205class Alignment(HashableObject):
206 """Alignment options for use in styles."""
207 HORIZONTAL_GENERAL = 'general'
208 HORIZONTAL_LEFT = 'left'
209 HORIZONTAL_RIGHT = 'right'
210 HORIZONTAL_CENTER = 'center'
211 HORIZONTAL_CENTER_CONTINUOUS = 'centerContinuous'
212 HORIZONTAL_JUSTIFY = 'justify'
213 VERTICAL_BOTTOM = 'bottom'
214 VERTICAL_TOP = 'top'
215 VERTICAL_CENTER = 'center'
216 VERTICAL_JUSTIFY = 'justify'
217
218 __fields__ = ('horizontal',
219 'vertical',
220 'text_rotation',
221 'wrap_text',
222 'shrink_to_fit',
223 'indent')
224 __slots__ = __fields__
225 __leaf__ = True
226
227 def __init__(self):
228 super(Alignment, self).__init__()
229 self.horizontal = self.HORIZONTAL_GENERAL
230 self.vertical = self.VERTICAL_BOTTOM
231 self.text_rotation = 0
232 self.wrap_text = False
233 self.shrink_to_fit = False
234 self.indent = 0
235
236
237class NumberFormat(HashableObject):
238 """Numer formatting for use in styles."""
239 FORMAT_GENERAL = 'General'
240 FORMAT_TEXT = '@'
241 FORMAT_NUMBER = '0'
242 FORMAT_NUMBER_00 = '0.00'
243 FORMAT_NUMBER_COMMA_SEPARATED1 = '#,##0.00'
244 FORMAT_NUMBER_COMMA_SEPARATED2 = '#,##0.00_-'
245 FORMAT_PERCENTAGE = '0%'
246 FORMAT_PERCENTAGE_00 = '0.00%'
247 FORMAT_DATE_YYYYMMDD2 = 'yyyy-mm-dd'
248 FORMAT_DATE_YYYYMMDD = 'yy-mm-dd'
249 FORMAT_DATE_DDMMYYYY = 'dd/mm/yy'
250 FORMAT_DATE_DMYSLASH = 'd/m/y'
251 FORMAT_DATE_DMYMINUS = 'd-m-y'
252 FORMAT_DATE_DMMINUS = 'd-m'
253 FORMAT_DATE_MYMINUS = 'm-y'
254 FORMAT_DATE_XLSX14 = 'mm-dd-yy'
255 FORMAT_DATE_XLSX15 = 'd-mmm-yy'
256 FORMAT_DATE_XLSX16 = 'd-mmm'
257 FORMAT_DATE_XLSX17 = 'mmm-yy'
258 FORMAT_DATE_XLSX22 = 'm/d/yy h:mm'
259 FORMAT_DATE_DATETIME = 'd/m/y h:mm'
260 FORMAT_DATE_TIME1 = 'h:mm AM/PM'
261 FORMAT_DATE_TIME2 = 'h:mm:ss AM/PM'
262 FORMAT_DATE_TIME3 = 'h:mm'
263 FORMAT_DATE_TIME4 = 'h:mm:ss'
264 FORMAT_DATE_TIME5 = 'mm:ss'
265 FORMAT_DATE_TIME6 = 'h:mm:ss'
266 FORMAT_DATE_TIME7 = 'i:s.S'
267 FORMAT_DATE_TIME8 = 'h:mm:ss@'
268 FORMAT_DATE_YYYYMMDDSLASH = 'yy/mm/dd@'
269 FORMAT_CURRENCY_USD_SIMPLE = '"$"#,##0.00_-'
270 FORMAT_CURRENCY_USD = '$#,##0_-'
271 FORMAT_CURRENCY_EUR_SIMPLE = '[$EUR ]#,##0.00_-'
272 _BUILTIN_FORMATS = {
273 0: 'General',
274 1: '0',
275 2: '0.00',
276 3: '#,##0',
277 4: '#,##0.00',
278
279 9: '0%',
280 10: '0.00%',
281 11: '0.00E+00',
282 12: '# ?/?',
283 13: '# ??/??',
284 14: 'mm-dd-yy',
285 15: 'd-mmm-yy',
286 16: 'd-mmm',
287 17: 'mmm-yy',
288 18: 'h:mm AM/PM',
289 19: 'h:mm:ss AM/PM',
290 20: 'h:mm',
291 21: 'h:mm:ss',
292 22: 'm/d/yy h:mm',
293
294 37: '#,##0 (#,##0)',
295 38: '#,##0 [Red](#,##0)',
296 39: '#,##0.00(#,##0.00)',
297 40: '#,##0.00[Red](#,##0.00)',
298
299 41: '_(* #,##0_);_(* \(#,##0\);_(* "-"_);_(@_)',
300 42: '_("$"* #,##0_);_("$"* \(#,##0\);_("$"* "-"_);_(@_)',
301 43: '_(* #,##0.00_);_(* \(#,##0.00\);_(* "-"??_);_(@_)',
302
303 44: '_("$"* #,##0.00_)_("$"* \(#,##0.00\)_("$"* "-"??_)_(@_)',
304 45: 'mm:ss',
305 46: '[h]:mm:ss',
306 47: 'mmss.0',
307 48: '##0.0E+0',
308 49: '@', }
309 _BUILTIN_FORMATS_REVERSE = dict(
310 [(value, key) for key, value in _BUILTIN_FORMATS.items()])
311
312 __fields__ = ('_format_code',
313 '_format_index')
314 __slots__ = __fields__
315 __leaf__ = True
316
317 DATE_INDICATORS = 'dmyhs'
318
319 def __init__(self):
320 super(NumberFormat, self).__init__()
321 self._format_code = self.FORMAT_GENERAL
322 self._format_index = 0
323
324 def _set_format_code(self, format_code = FORMAT_GENERAL):
325 """Setter for the format_code property."""
326 self._format_code = format_code
327 self._format_index = self.builtin_format_id(format = format_code)
328
329 def _get_format_code(self):
330 """Getter for the format_code property."""
331 return self._format_code
332
333 format_code = property(_get_format_code, _set_format_code)
334
335 def builtin_format_code(self, index):
336 """Return one of the standard format codes by index."""
337 return self._BUILTIN_FORMATS[index]
338
339 def is_builtin(self, format = None):
340 """Check if a format code is a standard format code."""
341 if format is None:
342 format = self._format_code
343 return format in self._BUILTIN_FORMATS.values()
344
345 def builtin_format_id(self, format):
346 """Return the id of a standard style."""
347 return self._BUILTIN_FORMATS_REVERSE.get(format, None)
348
349 def is_date_format(self, format = None):
350 """Check if the number format is actually representing a date."""
351 if format is None:
352 format = self._format_code
353
354 return any([x in format for x in self.DATE_INDICATORS])
355
356class Protection(HashableObject):
357 """Protection options for use in styles."""
358 PROTECTION_INHERIT = 'inherit'
359 PROTECTION_PROTECTED = 'protected'
360 PROTECTION_UNPROTECTED = 'unprotected'
361
362 __fields__ = ('locked',
363 'hidden')
364 __slots__ = __fields__
365 __leaf__ = True
366
367 def __init__(self):
368 super(Protection, self).__init__()
369 self.locked = self.PROTECTION_INHERIT
370 self.hidden = self.PROTECTION_INHERIT
371
372
373class Style(HashableObject):
374 """Style object containing all formatting details."""
375 __fields__ = ('font',
376 'fill',
377 'borders',
378 'alignment',
379 'number_format',
380 'protection')
381 __slots__ = __fields__
382
383 def __init__(self):
384 super(Style, self).__init__()
385 self.font = Font()
386 self.fill = Fill()
387 self.borders = Borders()
388 self.alignment = Alignment()
389 self.number_format = NumberFormat()
390 self.protection = Protection()
391
392DEFAULTS = Style()