table.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. from __future__ import absolute_import
  2. # Copyright (c) 2010-2019 openpyxl
  3. from openpyxl.descriptors.serialisable import Serialisable
  4. from openpyxl.descriptors import (
  5. Descriptor,
  6. Alias,
  7. Typed,
  8. Bool,
  9. Integer,
  10. NoneSet,
  11. String,
  12. Sequence,
  13. )
  14. from openpyxl.descriptors.excel import ExtensionList, CellRange
  15. from openpyxl.descriptors.sequence import NestedSequence
  16. from openpyxl.xml.constants import SHEET_MAIN_NS, REL_NS
  17. from openpyxl.xml.functions import tostring
  18. from openpyxl.utils import range_boundaries
  19. from openpyxl.utils.escape import escape, unescape
  20. from .related import Related
  21. from .filters import (
  22. AutoFilter,
  23. SortState,
  24. )
  25. TABLESTYLES = tuple(
  26. ["TableStyleMedium{0}".format(i) for i in range(1, 29)]
  27. + ["TableStyleLight{0}".format(i) for i in range(1, 22)]
  28. + ["TableStyleDark{0}".format(i) for i in range(1, 12)]
  29. )
  30. PIVOTSTYLES = tuple(
  31. ["PivotStyleMedium{0}".format(i) for i in range(1, 29)]
  32. + ["PivotStyleLight{0}".format(i) for i in range(1, 29)]
  33. + ["PivotStyleDark{0}".format(i) for i in range(1, 29)]
  34. )
  35. class TableStyleInfo(Serialisable):
  36. tagname = "tableStyleInfo"
  37. name = String(allow_none=True)
  38. showFirstColumn = Bool(allow_none=True)
  39. showLastColumn = Bool(allow_none=True)
  40. showRowStripes = Bool(allow_none=True)
  41. showColumnStripes = Bool(allow_none=True)
  42. def __init__(self,
  43. name=None,
  44. showFirstColumn=None,
  45. showLastColumn=None,
  46. showRowStripes=None,
  47. showColumnStripes=None,
  48. ):
  49. self.name = name
  50. self.showFirstColumn = showFirstColumn
  51. self.showLastColumn = showLastColumn
  52. self.showRowStripes = showRowStripes
  53. self.showColumnStripes = showColumnStripes
  54. class XMLColumnProps(Serialisable):
  55. tagname = "xmlColumnPr"
  56. mapId = Integer()
  57. xpath = String()
  58. denormalized = Bool(allow_none=True)
  59. xmlDataType = String()
  60. extLst = Typed(expected_type=ExtensionList, allow_none=True)
  61. __elements__ = ()
  62. def __init__(self,
  63. mapId=None,
  64. xpath=None,
  65. denormalized=None,
  66. xmlDataType=None,
  67. extLst=None,
  68. ):
  69. self.mapId = mapId
  70. self.xpath = xpath
  71. self.denormalized = denormalized
  72. self.xmlDataType = xmlDataType
  73. class TableFormula(Serialisable):
  74. tagname = "tableFormula"
  75. ## Note formula is stored as the text value
  76. array = Bool(allow_none=True)
  77. attr_text = Descriptor()
  78. text = Alias('attr_text')
  79. def __init__(self,
  80. array=None,
  81. attr_text=None,
  82. ):
  83. self.array = array
  84. self.attr_text = attr_text
  85. class TableColumn(Serialisable):
  86. tagname = "tableColumn"
  87. id = Integer()
  88. uniqueName = String(allow_none=True)
  89. name = String()
  90. totalsRowFunction = NoneSet(values=(['sum', 'min', 'max', 'average',
  91. 'count', 'countNums', 'stdDev', 'var', 'custom']))
  92. totalsRowLabel = String(allow_none=True)
  93. queryTableFieldId = Integer(allow_none=True)
  94. headerRowDxfId = Integer(allow_none=True)
  95. dataDxfId = Integer(allow_none=True)
  96. totalsRowDxfId = Integer(allow_none=True)
  97. headerRowCellStyle = String(allow_none=True)
  98. dataCellStyle = String(allow_none=True)
  99. totalsRowCellStyle = String(allow_none=True)
  100. calculatedColumnFormula = Typed(expected_type=TableFormula, allow_none=True)
  101. totalsRowFormula = Typed(expected_type=TableFormula, allow_none=True)
  102. xmlColumnPr = Typed(expected_type=XMLColumnProps, allow_none=True)
  103. extLst = Typed(expected_type=ExtensionList, allow_none=True)
  104. __elements__ = ('calculatedColumnFormula', 'totalsRowFormula',
  105. 'xmlColumnPr', 'extLst')
  106. def __init__(self,
  107. id=None,
  108. uniqueName=None,
  109. name=None,
  110. totalsRowFunction=None,
  111. totalsRowLabel=None,
  112. queryTableFieldId=None,
  113. headerRowDxfId=None,
  114. dataDxfId=None,
  115. totalsRowDxfId=None,
  116. headerRowCellStyle=None,
  117. dataCellStyle=None,
  118. totalsRowCellStyle=None,
  119. calculatedColumnFormula=None,
  120. totalsRowFormula=None,
  121. xmlColumnPr=None,
  122. extLst=None,
  123. ):
  124. self.id = id
  125. self.uniqueName = uniqueName
  126. self.name = name
  127. self.totalsRowFunction = totalsRowFunction
  128. self.totalsRowLabel = totalsRowLabel
  129. self.queryTableFieldId = queryTableFieldId
  130. self.headerRowDxfId = headerRowDxfId
  131. self.dataDxfId = dataDxfId
  132. self.totalsRowDxfId = totalsRowDxfId
  133. self.headerRowCellStyle = headerRowCellStyle
  134. self.dataCellStyle = dataCellStyle
  135. self.totalsRowCellStyle = totalsRowCellStyle
  136. self.calculatedColumnFormula = calculatedColumnFormula
  137. self.totalsRowFormula = totalsRowFormula
  138. self.xmlColumnPr = xmlColumnPr
  139. self.extLst = extLst
  140. def __iter__(self):
  141. for k, v in super(TableColumn, self).__iter__():
  142. if k == 'name':
  143. v = escape(v)
  144. yield k, v
  145. @classmethod
  146. def from_tree(cls, node):
  147. self = super(TableColumn, cls).from_tree(node)
  148. self.name = unescape(self.name)
  149. return self
  150. class TableNameDescriptor(String):
  151. """
  152. Table names cannot have spaces in them
  153. """
  154. def __set__(self, instance, value):
  155. if value is not None and " " in value:
  156. raise ValueError("Table names cannot have spaces")
  157. super(TableNameDescriptor, self).__set__(instance, value)
  158. class Table(Serialisable):
  159. _path = "/tables/table{0}.xml"
  160. mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml"
  161. _rel_type = REL_NS + "/table"
  162. _rel_id = None
  163. tagname = "table"
  164. id = Integer()
  165. name = String(allow_none=True)
  166. displayName = TableNameDescriptor()
  167. comment = String(allow_none=True)
  168. ref = CellRange()
  169. tableType = NoneSet(values=(['worksheet', 'xml', 'queryTable']))
  170. headerRowCount = Integer(allow_none=True)
  171. insertRow = Bool(allow_none=True)
  172. insertRowShift = Bool(allow_none=True)
  173. totalsRowCount = Integer(allow_none=True)
  174. totalsRowShown = Bool(allow_none=True)
  175. published = Bool(allow_none=True)
  176. headerRowDxfId = Integer(allow_none=True)
  177. dataDxfId = Integer(allow_none=True)
  178. totalsRowDxfId = Integer(allow_none=True)
  179. headerRowBorderDxfId = Integer(allow_none=True)
  180. tableBorderDxfId = Integer(allow_none=True)
  181. totalsRowBorderDxfId = Integer(allow_none=True)
  182. headerRowCellStyle = String(allow_none=True)
  183. dataCellStyle = String(allow_none=True)
  184. totalsRowCellStyle = String(allow_none=True)
  185. connectionId = Integer(allow_none=True)
  186. autoFilter = Typed(expected_type=AutoFilter, allow_none=True)
  187. sortState = Typed(expected_type=SortState, allow_none=True)
  188. tableColumns = NestedSequence(expected_type=TableColumn, count=True)
  189. tableStyleInfo = Typed(expected_type=TableStyleInfo, allow_none=True)
  190. extLst = Typed(expected_type=ExtensionList, allow_none=True)
  191. __elements__ = ('autoFilter', 'sortState', 'tableColumns',
  192. 'tableStyleInfo')
  193. def __init__(self,
  194. id=1,
  195. displayName=None,
  196. ref=None,
  197. name=None,
  198. comment=None,
  199. tableType=None,
  200. headerRowCount=1,
  201. insertRow=None,
  202. insertRowShift=None,
  203. totalsRowCount=None,
  204. totalsRowShown=None,
  205. published=None,
  206. headerRowDxfId=None,
  207. dataDxfId=None,
  208. totalsRowDxfId=None,
  209. headerRowBorderDxfId=None,
  210. tableBorderDxfId=None,
  211. totalsRowBorderDxfId=None,
  212. headerRowCellStyle=None,
  213. dataCellStyle=None,
  214. totalsRowCellStyle=None,
  215. connectionId=None,
  216. autoFilter=None,
  217. sortState=None,
  218. tableColumns=(),
  219. tableStyleInfo=None,
  220. extLst=None,
  221. ):
  222. self.id = id
  223. self.displayName = displayName
  224. if name is None:
  225. name = displayName
  226. self.name = name
  227. self.comment = comment
  228. self.ref = ref
  229. self.tableType = tableType
  230. self.headerRowCount = headerRowCount
  231. self.insertRow = insertRow
  232. self.insertRowShift = insertRowShift
  233. self.totalsRowCount = totalsRowCount
  234. self.totalsRowShown = totalsRowShown
  235. self.published = published
  236. self.headerRowDxfId = headerRowDxfId
  237. self.dataDxfId = dataDxfId
  238. self.totalsRowDxfId = totalsRowDxfId
  239. self.headerRowBorderDxfId = headerRowBorderDxfId
  240. self.tableBorderDxfId = tableBorderDxfId
  241. self.totalsRowBorderDxfId = totalsRowBorderDxfId
  242. self.headerRowCellStyle = headerRowCellStyle
  243. self.dataCellStyle = dataCellStyle
  244. self.totalsRowCellStyle = totalsRowCellStyle
  245. self.connectionId = connectionId
  246. self.autoFilter = autoFilter
  247. self.sortState = sortState
  248. self.tableColumns = tableColumns
  249. self.tableStyleInfo = tableStyleInfo
  250. def to_tree(self):
  251. tree = super(Table, self).to_tree()
  252. tree.set("xmlns", SHEET_MAIN_NS)
  253. return tree
  254. @property
  255. def path(self):
  256. """
  257. Return path within the archive
  258. """
  259. return "/xl" + self._path.format(self.id)
  260. def _write(self, archive):
  261. """
  262. Serialise to XML and write to archive
  263. """
  264. xml = self.to_tree()
  265. archive.writestr(self.path[1:], tostring(xml))
  266. def _initialise_columns(self):
  267. """
  268. Create a list of table columns from a cell range
  269. Always set a ref if we have headers (the default)
  270. Column headings must be strings and must match cells in the worksheet.
  271. """
  272. min_col, min_row, max_col, max_row = range_boundaries(self.ref)
  273. for idx in range(min_col, max_col+1):
  274. col = TableColumn(id=idx, name="Column{0}".format(idx))
  275. self.tableColumns.append(col)
  276. if self.headerRowCount:
  277. self.autoFilter = AutoFilter(ref=self.ref)
  278. class TablePartList(Serialisable):
  279. tagname = "tableParts"
  280. count = Integer(allow_none=True)
  281. tablePart = Sequence(expected_type=Related)
  282. __elements__ = ('tablePart',)
  283. __attrs__ = ('count',)
  284. def __init__(self,
  285. count=None,
  286. tablePart=(),
  287. ):
  288. self.tablePart = tablePart
  289. def append(self, part):
  290. self.tablePart.append(part)
  291. @property
  292. def count(self):
  293. return len(self.tablePart)
  294. def __bool__(self):
  295. return bool(self.tablePart)
  296. __nonzero__ = __bool__