workbook.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  1. from __future__ import absolute_import
  2. # Copyright (c) 2010-2019 openpyxl
  3. """Workbook is the top-level container for all document information."""
  4. from copy import copy
  5. from openpyxl.compat import deprecated, long
  6. from openpyxl.worksheet.worksheet import Worksheet
  7. from openpyxl.worksheet._read_only import ReadOnlyWorksheet
  8. from openpyxl.worksheet._write_only import WriteOnlyWorksheet
  9. from openpyxl.worksheet.copier import WorksheetCopy
  10. from openpyxl.utils import quote_sheetname
  11. from openpyxl.utils.indexed_list import IndexedList
  12. from openpyxl.utils.datetime import CALENDAR_WINDOWS_1900
  13. from openpyxl.utils.exceptions import ReadOnlyWorkbookException
  14. from openpyxl.writer.excel import save_workbook
  15. from openpyxl.styles.cell_style import StyleArray
  16. from openpyxl.styles.named_styles import NamedStyle
  17. from openpyxl.styles.differential import DifferentialStyleList
  18. from openpyxl.styles.alignment import Alignment
  19. from openpyxl.styles.borders import DEFAULT_BORDER
  20. from openpyxl.styles.fills import DEFAULT_EMPTY_FILL, DEFAULT_GRAY_FILL
  21. from openpyxl.styles.fonts import DEFAULT_FONT
  22. from openpyxl.styles.protection import Protection
  23. from openpyxl.styles.colors import COLOR_INDEX
  24. from openpyxl.styles.named_styles import NamedStyleList
  25. from openpyxl.styles.table import TableStyleList
  26. from openpyxl.chartsheet import Chartsheet
  27. from .defined_name import DefinedName, DefinedNameList
  28. from openpyxl.packaging.core import DocumentProperties
  29. from openpyxl.packaging.relationship import RelationshipList
  30. from .child import _WorkbookChild
  31. from .protection import DocumentSecurity
  32. from .properties import CalcProperties
  33. from .views import BookView
  34. from openpyxl.xml.constants import (
  35. XLSM,
  36. XLSX,
  37. XLTM,
  38. XLTX
  39. )
  40. INTEGER_TYPES = (int, long)
  41. class Workbook(object):
  42. """Workbook is the container for all other parts of the document."""
  43. _read_only = False
  44. _data_only = False
  45. template = False
  46. path = "/xl/workbook.xml"
  47. def __init__(self,
  48. write_only=False,
  49. iso_dates=False,
  50. ):
  51. self._sheets = []
  52. self._pivots = []
  53. self._active_sheet_index = 0
  54. self.defined_names = DefinedNameList()
  55. self._external_links = []
  56. self.properties = DocumentProperties()
  57. self.security = DocumentSecurity()
  58. self.__write_only = write_only
  59. self.shared_strings = IndexedList()
  60. self._setup_styles()
  61. self.loaded_theme = None
  62. self.vba_archive = None
  63. self.is_template = False
  64. self.code_name = None
  65. self.epoch = CALENDAR_WINDOWS_1900
  66. self.encoding = "utf-8"
  67. self.iso_dates = iso_dates
  68. if not self.write_only:
  69. self._sheets.append(Worksheet(self))
  70. self.rels = RelationshipList()
  71. self.calculation = CalcProperties()
  72. self.views = [BookView()]
  73. def _setup_styles(self):
  74. """Bootstrap styles"""
  75. self._fonts = IndexedList()
  76. self._fonts.add(DEFAULT_FONT)
  77. self._alignments = IndexedList([Alignment()])
  78. self._borders = IndexedList()
  79. self._borders.add(DEFAULT_BORDER)
  80. self._fills = IndexedList()
  81. self._fills.add(DEFAULT_EMPTY_FILL)
  82. self._fills.add(DEFAULT_GRAY_FILL)
  83. self._number_formats = IndexedList()
  84. self._date_formats = {}
  85. self._protections = IndexedList([Protection()])
  86. self._colors = COLOR_INDEX
  87. self._cell_styles = IndexedList([StyleArray()])
  88. self._named_styles = NamedStyleList()
  89. self.add_named_style(NamedStyle(font=copy(DEFAULT_FONT), builtinId=0))
  90. self._table_styles = TableStyleList()
  91. self._differential_styles = DifferentialStyleList()
  92. @property
  93. def read_only(self):
  94. return self._read_only
  95. @property
  96. def data_only(self):
  97. return self._data_only
  98. @property
  99. def write_only(self):
  100. return self.__write_only
  101. @property
  102. def guess_types(self):
  103. return getattr(self, '__guess_types', False)
  104. @guess_types.setter
  105. def guess_types(self, value):
  106. self.__guess_types = value
  107. @deprecated("Use the .active property")
  108. def get_active_sheet(self):
  109. """Returns the current active sheet."""
  110. return self.active
  111. @property
  112. def excel_base_date(self):
  113. return self.epoch
  114. @property
  115. def active(self):
  116. """Get the currently active sheet or None
  117. :type: :class:`openpyxl.worksheet.worksheet.Worksheet`
  118. """
  119. try:
  120. return self._sheets[self._active_sheet_index]
  121. except IndexError:
  122. pass
  123. @active.setter
  124. def active(self, value):
  125. """Set the active sheet"""
  126. if not isinstance(value, (_WorkbookChild, INTEGER_TYPES)):
  127. raise TypeError("Value must be either a worksheet, chartsheet or numerical index")
  128. if isinstance(value, INTEGER_TYPES):
  129. self._active_sheet_index = value
  130. return
  131. #if self._sheets and 0 <= value < len(self._sheets):
  132. #value = self._sheets[value]
  133. #else:
  134. #raise ValueError("Sheet index is outside the range of possible values", value)
  135. if value not in self._sheets:
  136. raise ValueError("Worksheet is not in the workbook")
  137. if value.sheet_state != "visible":
  138. raise ValueError("Only visible sheets can be made active")
  139. idx = self._sheets.index(value)
  140. self._active_sheet_index = idx
  141. def create_sheet(self, title=None, index=None):
  142. """Create a worksheet (at an optional index).
  143. :param title: optional title of the sheet
  144. :type title: unicode
  145. :param index: optional position at which the sheet will be inserted
  146. :type index: int
  147. """
  148. if self.read_only:
  149. raise ReadOnlyWorkbookException('Cannot create new sheet in a read-only workbook')
  150. if self.write_only :
  151. new_ws = WriteOnlyWorksheet(parent=self, title=title)
  152. else:
  153. new_ws = Worksheet(parent=self, title=title)
  154. self._add_sheet(sheet=new_ws, index=index)
  155. return new_ws
  156. def _add_sheet(self, sheet, index=None):
  157. """Add an worksheet (at an optional index)."""
  158. if not isinstance(sheet, (Worksheet, WriteOnlyWorksheet, Chartsheet)):
  159. raise TypeError("Cannot be added to a workbook")
  160. if sheet.parent != self:
  161. raise ValueError("You cannot add worksheets from another workbook.")
  162. if index is None:
  163. self._sheets.append(sheet)
  164. else:
  165. self._sheets.insert(index, sheet)
  166. def move_sheet(self, sheet, offset=0):
  167. """
  168. Move a sheet or sheetname
  169. """
  170. if not isinstance(sheet, Worksheet):
  171. sheet = self[sheet]
  172. idx = self._sheets.index(sheet)
  173. del self._sheets[idx]
  174. new_pos = idx + offset
  175. self._sheets.insert(new_pos, sheet)
  176. def remove(self, worksheet):
  177. """Remove `worksheet` from this workbook."""
  178. idx = self._sheets.index(worksheet)
  179. localnames = self.defined_names.localnames(scope=idx)
  180. for name in localnames:
  181. self.defined_names.delete(name, scope=idx)
  182. self._sheets.remove(worksheet)
  183. @deprecated("Use wb.remove(worksheet) or del wb[sheetname]")
  184. def remove_sheet(self, worksheet):
  185. """Remove `worksheet` from this workbook."""
  186. self.remove(worksheet)
  187. def create_chartsheet(self, title=None, index=None):
  188. if self.read_only:
  189. raise ReadOnlyWorkbookException("Cannot create new sheet in a read-only workbook")
  190. cs = Chartsheet(parent=self, title=title)
  191. self._add_sheet(cs, index)
  192. return cs
  193. @deprecated("Use wb[sheetname]")
  194. def get_sheet_by_name(self, name):
  195. """Returns a worksheet by its name.
  196. :param name: the name of the worksheet to look for
  197. :type name: string
  198. """
  199. return self[name]
  200. def __contains__(self, key):
  201. return key in set(self.sheetnames)
  202. def index(self, worksheet):
  203. """Return the index of a worksheet."""
  204. return self.worksheets.index(worksheet)
  205. @deprecated("Use wb.index(worksheet)")
  206. def get_index(self, worksheet):
  207. """Return the index of the worksheet."""
  208. return self.index(worksheet)
  209. def __getitem__(self, key):
  210. """Returns a worksheet by its name.
  211. :param name: the name of the worksheet to look for
  212. :type name: string
  213. """
  214. for sheet in self.worksheets + self.chartsheets:
  215. if sheet.title == key:
  216. return sheet
  217. raise KeyError("Worksheet {0} does not exist.".format(key))
  218. def __delitem__(self, key):
  219. sheet = self[key]
  220. self.remove(sheet)
  221. def __iter__(self):
  222. return iter(self.worksheets)
  223. @deprecated("Use wb.sheetnames")
  224. def get_sheet_names(self):
  225. return self.sheetnames
  226. @property
  227. def worksheets(self):
  228. """A list of sheets in this workbook
  229. :type: list of :class:`openpyxl.worksheet.worksheet.Worksheet`
  230. """
  231. return [s for s in self._sheets if isinstance(s, (Worksheet, ReadOnlyWorksheet, WriteOnlyWorksheet))]
  232. @property
  233. def chartsheets(self):
  234. """A list of Chartsheets in this workbook
  235. :type: list of :class:`openpyxl.chartsheet.chartsheet.Chartsheet`
  236. """
  237. return [s for s in self._sheets if isinstance(s, Chartsheet)]
  238. @property
  239. def sheetnames(self):
  240. """Returns the list of the names of worksheets in this workbook.
  241. Names are returned in the worksheets order.
  242. :type: list of strings
  243. """
  244. return [s.title for s in self._sheets]
  245. def create_named_range(self, name, worksheet=None, value=None, scope=None):
  246. """Create a new named_range on a worksheet"""
  247. defn = DefinedName(name=name, localSheetId=scope)
  248. if worksheet is not None:
  249. defn.value = "{0}!{1}".format(quote_sheetname(worksheet.title), value)
  250. else:
  251. defn.value = value
  252. self.defined_names.append(defn)
  253. def add_named_style(self, style):
  254. """
  255. Add a named style
  256. """
  257. self._named_styles.append(style)
  258. style.bind(self)
  259. @property
  260. def named_styles(self):
  261. """
  262. List available named styles
  263. """
  264. return self._named_styles.names
  265. @deprecated("Use workbook.defined_names.definedName")
  266. def get_named_ranges(self):
  267. """Return all named ranges"""
  268. return self.defined_names.definedName
  269. @deprecated("Use workbook.defined_names.append")
  270. def add_named_range(self, named_range):
  271. """Add an existing named_range to the list of named_ranges."""
  272. self.defined_names.append(named_range)
  273. @deprecated("Use workbook.defined_names[name]")
  274. def get_named_range(self, name):
  275. """Return the range specified by name."""
  276. return self.defined_names[name]
  277. @deprecated("Use del workbook.defined_names[name]")
  278. def remove_named_range(self, named_range):
  279. """Remove a named_range from this workbook."""
  280. del self.defined_names[named_range]
  281. @property
  282. def mime_type(self):
  283. """
  284. The mime type is determined by whether a workbook is a template or
  285. not and whether it contains macros or not. Excel requires the file
  286. extension to match but openpyxl does not enforce this.
  287. """
  288. ct = self.template and XLTX or XLSX
  289. if self.vba_archive:
  290. ct = self.template and XLTM or XLSM
  291. return ct
  292. def save(self, filename):
  293. """Save the current workbook under the given `filename`.
  294. Use this function instead of using an `ExcelWriter`.
  295. .. warning::
  296. When creating your workbook using `write_only` set to True,
  297. you will only be able to call this function once. Subsequents attempts to
  298. modify or save the file will raise an :class:`openpyxl.shared.exc.WorkbookAlreadySaved` exception.
  299. """
  300. if self.read_only:
  301. raise TypeError("""Workbook is read-only""")
  302. if self.write_only and not self.worksheets:
  303. self.create_sheet()
  304. save_workbook(self, filename)
  305. @property
  306. def style_names(self):
  307. """
  308. List of named styles
  309. """
  310. return [s.name for s in self._named_styles]
  311. def copy_worksheet(self, from_worksheet):
  312. """Copy an existing worksheet in the current workbook
  313. .. warning::
  314. This function cannot copy worksheets between workbooks.
  315. worksheets can only be copied within the workbook that they belong
  316. :param from_worksheet: the worksheet to be copied from
  317. :return: copy of the initial worksheet
  318. """
  319. if self.__write_only or self._read_only:
  320. raise ValueError("Cannot copy worksheets in read-only or write-only mode")
  321. new_title = u"{0} Copy".format(from_worksheet.title)
  322. to_worksheet = self.create_sheet(title=new_title)
  323. cp = WorksheetCopy(source_worksheet=from_worksheet, target_worksheet=to_worksheet)
  324. cp.copy_worksheet()
  325. return to_worksheet
  326. def close(self):
  327. """
  328. Close workbook file if open. Only affects read-only and write-only modes.
  329. """
  330. if hasattr(self, '_archive'):
  331. self._archive.close()