dimensions.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. # Copyright (c) 2010-2024 openpyxl
  2. from copy import copy
  3. from openpyxl.compat import safe_string
  4. from openpyxl.utils import (
  5. get_column_letter,
  6. get_column_interval,
  7. column_index_from_string,
  8. range_boundaries,
  9. )
  10. from openpyxl.utils.units import DEFAULT_COLUMN_WIDTH
  11. from openpyxl.descriptors import (
  12. Integer,
  13. Float,
  14. Bool,
  15. Strict,
  16. String,
  17. Alias,
  18. )
  19. from openpyxl.descriptors.serialisable import Serialisable
  20. from openpyxl.styles.styleable import StyleableObject
  21. from openpyxl.utils.bound_dictionary import BoundDictionary
  22. from openpyxl.xml.functions import Element
  23. class Dimension(Strict, StyleableObject):
  24. """Information about the display properties of a row or column."""
  25. __fields__ = ('hidden',
  26. 'outlineLevel',
  27. 'collapsed',)
  28. index = Integer()
  29. hidden = Bool()
  30. outlineLevel = Integer(allow_none=True)
  31. outline_level = Alias('outlineLevel')
  32. collapsed = Bool()
  33. style = Alias('style_id')
  34. def __init__(self, index, hidden, outlineLevel,
  35. collapsed, worksheet, visible=True, style=None):
  36. super().__init__(sheet=worksheet, style_array=style)
  37. self.index = index
  38. self.hidden = hidden
  39. self.outlineLevel = outlineLevel
  40. self.collapsed = collapsed
  41. def __iter__(self):
  42. for key in self.__fields__:
  43. value = getattr(self, key, None)
  44. if value:
  45. yield key, safe_string(value)
  46. def __copy__(self):
  47. cp = self.__new__(self.__class__)
  48. attrib = self.__dict__
  49. attrib['worksheet'] = self.parent
  50. cp.__init__(**attrib)
  51. cp._style = copy(self._style)
  52. return cp
  53. def __repr__(self):
  54. return f"<{self.__class__.__name__} Instance, Attributes={dict(self)}>"
  55. class RowDimension(Dimension):
  56. """Information about the display properties of a row."""
  57. __fields__ = Dimension.__fields__ + ('ht', 'customFormat', 'customHeight', 's',
  58. 'thickBot', 'thickTop')
  59. r = Alias('index')
  60. s = Alias('style_id')
  61. ht = Float(allow_none=True)
  62. height = Alias('ht')
  63. thickBot = Bool()
  64. thickTop = Bool()
  65. def __init__(self,
  66. worksheet,
  67. index=0,
  68. ht=None,
  69. customHeight=None, # do not write
  70. s=None,
  71. customFormat=None, # do not write
  72. hidden=False,
  73. outlineLevel=0,
  74. outline_level=None,
  75. collapsed=False,
  76. visible=None,
  77. height=None,
  78. r=None,
  79. spans=None,
  80. thickBot=None,
  81. thickTop=None,
  82. **kw
  83. ):
  84. if r is not None:
  85. index = r
  86. if height is not None:
  87. ht = height
  88. self.ht = ht
  89. if visible is not None:
  90. hidden = not visible
  91. if outline_level is not None:
  92. outlineLevel = outline_level
  93. self.thickBot = thickBot
  94. self.thickTop = thickTop
  95. super().__init__(index, hidden, outlineLevel,
  96. collapsed, worksheet, style=s)
  97. @property
  98. def customFormat(self):
  99. """Always true if there is a style for the row"""
  100. return self.has_style
  101. @property
  102. def customHeight(self):
  103. """Always true if there is a height for the row"""
  104. return self.ht is not None
  105. class ColumnDimension(Dimension):
  106. """Information about the display properties of a column."""
  107. width = Float()
  108. bestFit = Bool()
  109. auto_size = Alias('bestFit')
  110. index = String()
  111. min = Integer(allow_none=True)
  112. max = Integer(allow_none=True)
  113. collapsed = Bool()
  114. __fields__ = Dimension.__fields__ + ('width', 'bestFit', 'customWidth', 'style',
  115. 'min', 'max')
  116. def __init__(self,
  117. worksheet,
  118. index='A',
  119. width=DEFAULT_COLUMN_WIDTH,
  120. bestFit=False,
  121. hidden=False,
  122. outlineLevel=0,
  123. outline_level=None,
  124. collapsed=False,
  125. style=None,
  126. min=None,
  127. max=None,
  128. customWidth=False, # do not write
  129. visible=None,
  130. auto_size=None,):
  131. self.width = width
  132. self.min = min
  133. self.max = max
  134. if visible is not None:
  135. hidden = not visible
  136. if auto_size is not None:
  137. bestFit = auto_size
  138. self.bestFit = bestFit
  139. if outline_level is not None:
  140. outlineLevel = outline_level
  141. self.collapsed = collapsed
  142. super().__init__(index, hidden, outlineLevel,
  143. collapsed, worksheet, style=style)
  144. @property
  145. def customWidth(self):
  146. """Always true if there is a width for the column"""
  147. return bool(self.width)
  148. def reindex(self):
  149. """
  150. Set boundaries for column definition
  151. """
  152. if not all([self.min, self.max]):
  153. self.min = self.max = column_index_from_string(self.index)
  154. @property
  155. def range(self):
  156. """Return the range of cells actually covered"""
  157. return f"{get_column_letter(self.min)}:{get_column_letter(self.max)}"
  158. def to_tree(self):
  159. attrs = dict(self)
  160. if attrs.keys() != {'min', 'max'}:
  161. return Element("col", **attrs)
  162. class DimensionHolder(BoundDictionary):
  163. """
  164. Allow columns to be grouped
  165. """
  166. def __init__(self, worksheet, reference="index", default_factory=None):
  167. self.worksheet = worksheet
  168. self.max_outline = None
  169. self.default_factory = default_factory
  170. super().__init__(reference, default_factory)
  171. def group(self, start, end=None, outline_level=1, hidden=False):
  172. """allow grouping a range of consecutive rows or columns together
  173. :param start: first row or column to be grouped (mandatory)
  174. :param end: last row or column to be grouped (optional, default to start)
  175. :param outline_level: outline level
  176. :param hidden: should the group be hidden on workbook open or not
  177. """
  178. if end is None:
  179. end = start
  180. if isinstance(self.default_factory(), ColumnDimension):
  181. new_dim = self[start]
  182. new_dim.outline_level = outline_level
  183. new_dim.hidden = hidden
  184. work_sequence = get_column_interval(start, end)[1:]
  185. for column_letter in work_sequence:
  186. if column_letter in self:
  187. del self[column_letter]
  188. new_dim.min, new_dim.max = map(column_index_from_string, (start, end))
  189. elif isinstance(self.default_factory(), RowDimension):
  190. for el in range(start, end + 1):
  191. new_dim = self.worksheet.row_dimensions[el]
  192. new_dim.outline_level = outline_level
  193. new_dim.hidden = hidden
  194. def to_tree(self):
  195. def sorter(value):
  196. value.reindex()
  197. return value.min
  198. el = Element('cols')
  199. outlines = set()
  200. for col in sorted(self.values(), key=sorter):
  201. obj = col.to_tree()
  202. if obj is not None:
  203. outlines.add(col.outlineLevel)
  204. el.append(obj)
  205. if outlines:
  206. self.max_outline = max(outlines)
  207. if len(el):
  208. return el # must have at least one child
  209. class SheetFormatProperties(Serialisable):
  210. tagname = "sheetFormatPr"
  211. baseColWidth = Integer(allow_none=True)
  212. defaultColWidth = Float(allow_none=True)
  213. defaultRowHeight = Float()
  214. customHeight = Bool(allow_none=True)
  215. zeroHeight = Bool(allow_none=True)
  216. thickTop = Bool(allow_none=True)
  217. thickBottom = Bool(allow_none=True)
  218. outlineLevelRow = Integer(allow_none=True)
  219. outlineLevelCol = Integer(allow_none=True)
  220. def __init__(self,
  221. baseColWidth=8, #according to spec
  222. defaultColWidth=None,
  223. defaultRowHeight=15,
  224. customHeight=None,
  225. zeroHeight=None,
  226. thickTop=None,
  227. thickBottom=None,
  228. outlineLevelRow=None,
  229. outlineLevelCol=None,
  230. ):
  231. self.baseColWidth = baseColWidth
  232. self.defaultColWidth = defaultColWidth
  233. self.defaultRowHeight = defaultRowHeight
  234. self.customHeight = customHeight
  235. self.zeroHeight = zeroHeight
  236. self.thickTop = thickTop
  237. self.thickBottom = thickBottom
  238. self.outlineLevelRow = outlineLevelRow
  239. self.outlineLevelCol = outlineLevelCol
  240. class SheetDimension(Serialisable):
  241. tagname = "dimension"
  242. ref = String()
  243. def __init__(self,
  244. ref=None,
  245. ):
  246. self.ref = ref
  247. @property
  248. def boundaries(self):
  249. return range_boundaries(self.ref)