cell_style.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. # Copyright (c) 2010-2024 openpyxl
  2. from array import array
  3. from openpyxl.descriptors.serialisable import Serialisable
  4. from openpyxl.descriptors import (
  5. Typed,
  6. Float,
  7. Bool,
  8. Integer,
  9. Sequence,
  10. )
  11. from openpyxl.descriptors.excel import ExtensionList
  12. from openpyxl.utils.indexed_list import IndexedList
  13. from .alignment import Alignment
  14. from .protection import Protection
  15. class ArrayDescriptor:
  16. def __init__(self, key):
  17. self.key = key
  18. def __get__(self, instance, cls):
  19. return instance[self.key]
  20. def __set__(self, instance, value):
  21. instance[self.key] = value
  22. class StyleArray(array):
  23. """
  24. Simplified named tuple with an array
  25. """
  26. __slots__ = ()
  27. tagname = 'xf'
  28. fontId = ArrayDescriptor(0)
  29. fillId = ArrayDescriptor(1)
  30. borderId = ArrayDescriptor(2)
  31. numFmtId = ArrayDescriptor(3)
  32. protectionId = ArrayDescriptor(4)
  33. alignmentId = ArrayDescriptor(5)
  34. pivotButton = ArrayDescriptor(6)
  35. quotePrefix = ArrayDescriptor(7)
  36. xfId = ArrayDescriptor(8)
  37. def __new__(cls, args=[0]*9):
  38. return array.__new__(cls, 'i', args)
  39. def __hash__(self):
  40. return hash(tuple(self))
  41. def __copy__(self):
  42. return StyleArray((self))
  43. def __deepcopy__(self, memo):
  44. return StyleArray((self))
  45. class CellStyle(Serialisable):
  46. tagname = "xf"
  47. numFmtId = Integer()
  48. fontId = Integer()
  49. fillId = Integer()
  50. borderId = Integer()
  51. xfId = Integer(allow_none=True)
  52. quotePrefix = Bool(allow_none=True)
  53. pivotButton = Bool(allow_none=True)
  54. applyNumberFormat = Bool(allow_none=True)
  55. applyFont = Bool(allow_none=True)
  56. applyFill = Bool(allow_none=True)
  57. applyBorder = Bool(allow_none=True)
  58. applyAlignment = Bool(allow_none=True)
  59. applyProtection = Bool(allow_none=True)
  60. alignment = Typed(expected_type=Alignment, allow_none=True)
  61. protection = Typed(expected_type=Protection, allow_none=True)
  62. extLst = Typed(expected_type=ExtensionList, allow_none=True)
  63. __elements__ = ('alignment', 'protection')
  64. __attrs__ = ("numFmtId", "fontId", "fillId", "borderId",
  65. "applyAlignment", "applyProtection", "pivotButton", "quotePrefix", "xfId")
  66. def __init__(self,
  67. numFmtId=0,
  68. fontId=0,
  69. fillId=0,
  70. borderId=0,
  71. xfId=None,
  72. quotePrefix=None,
  73. pivotButton=None,
  74. applyNumberFormat=None,
  75. applyFont=None,
  76. applyFill=None,
  77. applyBorder=None,
  78. applyAlignment=None,
  79. applyProtection=None,
  80. alignment=None,
  81. protection=None,
  82. extLst=None,
  83. ):
  84. self.numFmtId = numFmtId
  85. self.fontId = fontId
  86. self.fillId = fillId
  87. self.borderId = borderId
  88. self.xfId = xfId
  89. self.quotePrefix = quotePrefix
  90. self.pivotButton = pivotButton
  91. self.applyNumberFormat = applyNumberFormat
  92. self.applyFont = applyFont
  93. self.applyFill = applyFill
  94. self.applyBorder = applyBorder
  95. self.alignment = alignment
  96. self.protection = protection
  97. def to_array(self):
  98. """
  99. Convert to StyleArray
  100. """
  101. style = StyleArray()
  102. for k in ("fontId", "fillId", "borderId", "numFmtId", "pivotButton",
  103. "quotePrefix", "xfId"):
  104. v = getattr(self, k, 0)
  105. if v is not None:
  106. setattr(style, k, v)
  107. return style
  108. @classmethod
  109. def from_array(cls, style):
  110. """
  111. Convert from StyleArray
  112. """
  113. return cls(numFmtId=style.numFmtId, fontId=style.fontId,
  114. fillId=style.fillId, borderId=style.borderId, xfId=style.xfId,
  115. quotePrefix=style.quotePrefix, pivotButton=style.pivotButton,)
  116. @property
  117. def applyProtection(self):
  118. return self.protection is not None or None
  119. @property
  120. def applyAlignment(self):
  121. return self.alignment is not None or None
  122. class CellStyleList(Serialisable):
  123. tagname = "cellXfs"
  124. __attrs__ = ("count",)
  125. count = Integer(allow_none=True)
  126. xf = Sequence(expected_type=CellStyle)
  127. alignment = Sequence(expected_type=Alignment)
  128. protection = Sequence(expected_type=Protection)
  129. __elements__ = ('xf',)
  130. def __init__(self,
  131. count=None,
  132. xf=(),
  133. ):
  134. self.xf = xf
  135. @property
  136. def count(self):
  137. return len(self.xf)
  138. def __getitem__(self, idx):
  139. try:
  140. return self.xf[idx]
  141. except IndexError:
  142. print((f"{idx} is out of range"))
  143. return self.xf[idx]
  144. def _to_array(self):
  145. """
  146. Extract protection and alignments, convert to style array
  147. """
  148. self.prots = IndexedList([Protection()])
  149. self.alignments = IndexedList([Alignment()])
  150. styles = [] # allow duplicates
  151. for xf in self.xf:
  152. style = xf.to_array()
  153. if xf.alignment is not None:
  154. style.alignmentId = self.alignments.add(xf.alignment)
  155. if xf.protection is not None:
  156. style.protectionId = self.prots.add(xf.protection)
  157. styles.append(style)
  158. return IndexedList(styles)