dimensions.py 8.6 KB

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