123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292 |
- from __future__ import absolute_import
- # Copyright (c) 2010-2019 openpyxl
- from openpyxl.compat import safe_string
- from openpyxl.descriptors import (
- Typed,
- Integer,
- Bool,
- String,
- Sequence,
- )
- from openpyxl.descriptors.excel import ExtensionList
- from openpyxl.descriptors.serialisable import Serialisable
- from .fills import PatternFill, Fill
- from .fonts import Font
- from .borders import Border
- from .alignment import Alignment
- from .protection import Protection
- from .numbers import (
- NumberFormatDescriptor,
- BUILTIN_FORMATS_MAX_SIZE,
- BUILTIN_FORMATS_REVERSE,
- )
- from .cell_style import (
- StyleArray,
- CellStyle,
- )
- class NamedStyle(Serialisable):
- """
- Named and editable styles
- """
- font = Typed(expected_type=Font)
- fill = Typed(expected_type=Fill)
- border = Typed(expected_type=Border)
- alignment = Typed(expected_type=Alignment)
- number_format = NumberFormatDescriptor()
- protection = Typed(expected_type=Protection)
- builtinId = Integer(allow_none=True)
- hidden = Bool(allow_none=True)
- xfId = Integer(allow_none=True)
- name = String()
- _wb = None
- _style = StyleArray()
- def __init__(self,
- name="Normal",
- font=Font(),
- fill=PatternFill(),
- border=Border(),
- alignment=Alignment(),
- number_format=None,
- protection=Protection(),
- builtinId=None,
- hidden=False,
- xfId=None,
- ):
- self.name = name
- self.font = font
- self.fill = fill
- self.border = border
- self.alignment = alignment
- self.number_format = number_format
- self.protection = protection
- self.builtinId = builtinId
- self.hidden = hidden
- self._wb = None
- self._style = StyleArray()
- def __setattr__(self, attr, value):
- super(NamedStyle, self).__setattr__(attr, value)
- if getattr(self, '_wb', None) and attr in (
- 'font', 'fill', 'border', 'alignment', 'number_format', 'protection',
- ):
- self._recalculate()
- def __iter__(self):
- for key in ('name', 'builtinId', 'hidden', 'xfId'):
- value = getattr(self, key, None)
- if value is not None:
- yield key, safe_string(value)
- @property
- def xfId(self):
- """
- Index of the style in the list of named styles
- """
- return self._style.xfId
- def _set_index(self, idx):
- """
- Allow the containing list to set the index
- """
- self._style.xfId = idx
- def bind(self, wb):
- """
- Bind a named style to a workbook
- """
- self._wb = wb
- self._recalculate()
- def _recalculate(self):
- self._style.fontId = self._wb._fonts.add(self.font)
- self._style.borderId = self._wb._borders.add(self.border)
- self._style.fillId = self._wb._fills.add(self.fill)
- self._style.protectionId = self._wb._protections.add(self.protection)
- self._style.alignmentId = self._wb._alignments.add(self.alignment)
- fmt = self.number_format
- if fmt in BUILTIN_FORMATS_REVERSE:
- fmt = BUILTIN_FORMATS_REVERSE[fmt]
- else:
- fmt = self._wb._number_formats.add(self.number_format) + (
- BUILTIN_FORMATS_MAX_SIZE)
- self._style.numFmtId = fmt
- def as_tuple(self):
- """Return a style array representing the current style"""
- return self._style
- def as_xf(self):
- """
- Return equivalent XfStyle
- """
- xf = CellStyle.from_array(self._style)
- xf.xfId = None
- xf.pivotButton = None
- xf.quotePrefix = None
- if self.alignment != Alignment():
- xf.alignment = self.alignment
- if self.protection != Protection():
- xf.protection = self.protection
- return xf
- def as_name(self):
- """
- Return relevant named style
- """
- named = _NamedCellStyle(
- name=self.name,
- builtinId=self.builtinId,
- hidden=self.hidden,
- xfId=self.xfId
- )
- return named
- class NamedStyleList(list):
- """
- Named styles are editable and can be applied to multiple objects
- As only the index is stored in referencing objects the order mus
- be preserved.
- """
- @property
- def names(self):
- return [s.name for s in self]
- def __getitem__(self, key):
- if isinstance(key, int):
- return super(NamedStyleList, self).__getitem__(key)
- names = self.names
- if key not in names:
- raise KeyError("No named style with the name{0} exists".format(key))
- for idx, name in enumerate(names):
- if name == key:
- return self[idx]
- def append(self, style):
- if not isinstance(style, NamedStyle):
- raise TypeError("""Only NamedStyle instances can be added""")
- elif style.name in self.names:
- raise ValueError("""Style {0} exists already""".format(style.name))
- style._set_index(len(self))
- super(NamedStyleList, self).append(style)
- class _NamedCellStyle(Serialisable):
- """
- Pointer-based representation of named styles in XML
- xfId refers to the corresponding CellStyleXfs
- Not used in client code.
- """
- tagname = "cellStyle"
- name = String()
- xfId = Integer()
- builtinId = Integer(allow_none=True)
- iLevel = Integer(allow_none=True)
- hidden = Bool(allow_none=True)
- customBuiltin = Bool(allow_none=True)
- extLst = Typed(expected_type=ExtensionList, allow_none=True)
- __elements__ = ()
- def __init__(self,
- name=None,
- xfId=None,
- builtinId=None,
- iLevel=None,
- hidden=None,
- customBuiltin=None,
- extLst=None,
- ):
- self.name = name
- self.xfId = xfId
- self.builtinId = builtinId
- self.iLevel = iLevel
- self.hidden = hidden
- self.customBuiltin = customBuiltin
- class _NamedCellStyleList(Serialisable):
- """
- Container for named cell style objects
- Not used in client code
- """
- tagname = "cellStyles"
- count = Integer(allow_none=True)
- cellStyle = Sequence(expected_type=_NamedCellStyle)
- __attrs__ = ("count",)
- def __init__(self,
- count=None,
- cellStyle=(),
- ):
- self.cellStyle = cellStyle
- @property
- def count(self):
- return len(self.cellStyle)
- @property
- def names(self):
- """
- Convert to NamedStyle objects and remove duplicates.
- In theory the highest xfId wins but in practice they are duplicates
- so it doesn't matter.
- """
- def sort_fn(v):
- return v.xfId
- styles = []
- names = set()
- for ns in sorted(self.cellStyle, key=sort_fn):
- if ns.name in names:
- continue
- style = NamedStyle(
- name=ns.name,
- hidden=ns.hidden,
- builtinId = ns.builtinId
- )
- names.add(ns.name)
- style._set_index(len(styles)) # assign xfId
- styles.append(style)
- return NamedStyleList(styles)
|