worksheet.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907
  1. # Copyright (c) 2010-2024 openpyxl
  2. """Worksheet is the 2nd-level container in Excel."""
  3. # Python stdlib imports
  4. from itertools import chain
  5. from operator import itemgetter
  6. from inspect import isgenerator
  7. from warnings import warn
  8. # compatibility imports
  9. from openpyxl.compat import (
  10. deprecated,
  11. )
  12. # package imports
  13. from openpyxl.utils import (
  14. column_index_from_string,
  15. get_column_letter,
  16. range_boundaries,
  17. coordinate_to_tuple,
  18. )
  19. from openpyxl.cell import Cell, MergedCell
  20. from openpyxl.formatting.formatting import ConditionalFormattingList
  21. from openpyxl.packaging.relationship import RelationshipList
  22. from openpyxl.workbook.child import _WorkbookChild
  23. from openpyxl.workbook.defined_name import (
  24. DefinedNameDict,
  25. )
  26. from openpyxl.formula.translate import Translator
  27. from .datavalidation import DataValidationList
  28. from .page import (
  29. PrintPageSetup,
  30. PageMargins,
  31. PrintOptions,
  32. )
  33. from .dimensions import (
  34. ColumnDimension,
  35. RowDimension,
  36. DimensionHolder,
  37. SheetFormatProperties,
  38. )
  39. from .protection import SheetProtection
  40. from .filters import AutoFilter
  41. from .views import (
  42. Pane,
  43. Selection,
  44. SheetViewList,
  45. )
  46. from .cell_range import MultiCellRange, CellRange
  47. from .merge import MergedCellRange
  48. from .properties import WorksheetProperties
  49. from .pagebreak import RowBreak, ColBreak
  50. from .scenario import ScenarioList
  51. from .table import TableList
  52. from .formula import ArrayFormula
  53. from .print_settings import (
  54. PrintTitles,
  55. ColRange,
  56. RowRange,
  57. PrintArea,
  58. )
  59. class Worksheet(_WorkbookChild):
  60. """Represents a worksheet.
  61. Do not create worksheets yourself,
  62. use :func:`openpyxl.workbook.Workbook.create_sheet` instead
  63. """
  64. _rel_type = "worksheet"
  65. _path = "/xl/worksheets/sheet{0}.xml"
  66. mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"
  67. BREAK_NONE = 0
  68. BREAK_ROW = 1
  69. BREAK_COLUMN = 2
  70. SHEETSTATE_VISIBLE = 'visible'
  71. SHEETSTATE_HIDDEN = 'hidden'
  72. SHEETSTATE_VERYHIDDEN = 'veryHidden'
  73. # Paper size
  74. PAPERSIZE_LETTER = '1'
  75. PAPERSIZE_LETTER_SMALL = '2'
  76. PAPERSIZE_TABLOID = '3'
  77. PAPERSIZE_LEDGER = '4'
  78. PAPERSIZE_LEGAL = '5'
  79. PAPERSIZE_STATEMENT = '6'
  80. PAPERSIZE_EXECUTIVE = '7'
  81. PAPERSIZE_A3 = '8'
  82. PAPERSIZE_A4 = '9'
  83. PAPERSIZE_A4_SMALL = '10'
  84. PAPERSIZE_A5 = '11'
  85. # Page orientation
  86. ORIENTATION_PORTRAIT = 'portrait'
  87. ORIENTATION_LANDSCAPE = 'landscape'
  88. def __init__(self, parent, title=None):
  89. _WorkbookChild.__init__(self, parent, title)
  90. self._setup()
  91. def _setup(self):
  92. self.row_dimensions = DimensionHolder(worksheet=self,
  93. default_factory=self._add_row)
  94. self.column_dimensions = DimensionHolder(worksheet=self,
  95. default_factory=self._add_column)
  96. self.row_breaks = RowBreak()
  97. self.col_breaks = ColBreak()
  98. self._cells = {}
  99. self._charts = []
  100. self._images = []
  101. self._rels = RelationshipList()
  102. self._drawing = None
  103. self._comments = []
  104. self.merged_cells = MultiCellRange()
  105. self._tables = TableList()
  106. self._pivots = []
  107. self.data_validations = DataValidationList()
  108. self._hyperlinks = []
  109. self.sheet_state = 'visible'
  110. self.page_setup = PrintPageSetup(worksheet=self)
  111. self.print_options = PrintOptions()
  112. self._print_rows = None
  113. self._print_cols = None
  114. self._print_area = PrintArea()
  115. self.page_margins = PageMargins()
  116. self.views = SheetViewList()
  117. self.protection = SheetProtection()
  118. self.defined_names = DefinedNameDict()
  119. self._current_row = 0
  120. self.auto_filter = AutoFilter()
  121. self.conditional_formatting = ConditionalFormattingList()
  122. self.legacy_drawing = None
  123. self.sheet_properties = WorksheetProperties()
  124. self.sheet_format = SheetFormatProperties()
  125. self.scenarios = ScenarioList()
  126. @property
  127. def sheet_view(self):
  128. return self.views.active
  129. @property
  130. def selected_cell(self):
  131. return self.sheet_view.selection[0].sqref
  132. @property
  133. def active_cell(self):
  134. return self.sheet_view.selection[0].activeCell
  135. @property
  136. def array_formulae(self):
  137. """Returns a dictionary of cells with array formulae and the cells in array"""
  138. result = {}
  139. for c in self._cells.values():
  140. if c.data_type == "f":
  141. if isinstance(c.value, ArrayFormula):
  142. result[c.coordinate] = c.value.ref
  143. return result
  144. @property
  145. def show_gridlines(self):
  146. return self.sheet_view.showGridLines
  147. @property
  148. def freeze_panes(self):
  149. if self.sheet_view.pane is not None:
  150. return self.sheet_view.pane.topLeftCell
  151. @freeze_panes.setter
  152. def freeze_panes(self, topLeftCell=None):
  153. if isinstance(topLeftCell, Cell):
  154. topLeftCell = topLeftCell.coordinate
  155. if topLeftCell == 'A1':
  156. topLeftCell = None
  157. if not topLeftCell:
  158. self.sheet_view.pane = None
  159. return
  160. row, column = coordinate_to_tuple(topLeftCell)
  161. view = self.sheet_view
  162. view.pane = Pane(topLeftCell=topLeftCell,
  163. activePane="topRight",
  164. state="frozen")
  165. view.selection[0].pane = "topRight"
  166. if column > 1:
  167. view.pane.xSplit = column - 1
  168. if row > 1:
  169. view.pane.ySplit = row - 1
  170. view.pane.activePane = 'bottomLeft'
  171. view.selection[0].pane = "bottomLeft"
  172. if column > 1:
  173. view.selection[0].pane = "bottomRight"
  174. view.pane.activePane = 'bottomRight'
  175. if row > 1 and column > 1:
  176. sel = list(view.selection)
  177. sel.insert(0, Selection(pane="topRight", activeCell=None, sqref=None))
  178. sel.insert(1, Selection(pane="bottomLeft", activeCell=None, sqref=None))
  179. view.selection = sel
  180. def cell(self, row, column, value=None):
  181. """
  182. Returns a cell object based on the given coordinates.
  183. Usage: cell(row=15, column=1, value=5)
  184. Calling `cell` creates cells in memory when they
  185. are first accessed.
  186. :param row: row index of the cell (e.g. 4)
  187. :type row: int
  188. :param column: column index of the cell (e.g. 3)
  189. :type column: int
  190. :param value: value of the cell (e.g. 5)
  191. :type value: numeric or time or string or bool or none
  192. :rtype: openpyxl.cell.cell.Cell
  193. """
  194. if row < 1 or column < 1:
  195. raise ValueError("Row or column values must be at least 1")
  196. cell = self._get_cell(row, column)
  197. if value is not None:
  198. cell.value = value
  199. return cell
  200. def _get_cell(self, row, column):
  201. """
  202. Internal method for getting a cell from a worksheet.
  203. Will create a new cell if one doesn't already exist.
  204. """
  205. if not 0 < row < 1048577:
  206. raise ValueError(f"Row numbers must be between 1 and 1048576. Row number supplied was {row}")
  207. coordinate = (row, column)
  208. if not coordinate in self._cells:
  209. cell = Cell(self, row=row, column=column)
  210. self._add_cell(cell)
  211. return self._cells[coordinate]
  212. def _add_cell(self, cell):
  213. """
  214. Internal method for adding cell objects.
  215. """
  216. column = cell.col_idx
  217. row = cell.row
  218. self._current_row = max(row, self._current_row)
  219. self._cells[(row, column)] = cell
  220. def __getitem__(self, key):
  221. """Convenience access by Excel style coordinates
  222. The key can be a single cell coordinate 'A1', a range of cells 'A1:D25',
  223. individual rows or columns 'A', 4 or ranges of rows or columns 'A:D',
  224. 4:10.
  225. Single cells will always be created if they do not exist.
  226. Returns either a single cell or a tuple of rows or columns.
  227. """
  228. if isinstance(key, slice):
  229. if not all([key.start, key.stop]):
  230. raise IndexError("{0} is not a valid coordinate or range".format(key))
  231. key = "{0}:{1}".format(key.start, key.stop)
  232. if isinstance(key, int):
  233. key = str(key
  234. )
  235. min_col, min_row, max_col, max_row = range_boundaries(key)
  236. if not any([min_col, min_row, max_col, max_row]):
  237. raise IndexError("{0} is not a valid coordinate or range".format(key))
  238. if min_row is None:
  239. cols = tuple(self.iter_cols(min_col, max_col))
  240. if min_col == max_col:
  241. cols = cols[0]
  242. return cols
  243. if min_col is None:
  244. rows = tuple(self.iter_rows(min_col=min_col, min_row=min_row,
  245. max_col=self.max_column, max_row=max_row))
  246. if min_row == max_row:
  247. rows = rows[0]
  248. return rows
  249. if ":" not in key:
  250. return self._get_cell(min_row, min_col)
  251. return tuple(self.iter_rows(min_row=min_row, min_col=min_col,
  252. max_row=max_row, max_col=max_col))
  253. def __setitem__(self, key, value):
  254. self[key].value = value
  255. def __iter__(self):
  256. return self.iter_rows()
  257. def __delitem__(self, key):
  258. row, column = coordinate_to_tuple(key)
  259. if (row, column) in self._cells:
  260. del self._cells[(row, column)]
  261. @property
  262. def min_row(self):
  263. """The minimum row index containing data (1-based)
  264. :type: int
  265. """
  266. min_row = 1
  267. if self._cells:
  268. min_row = min(self._cells)[0]
  269. return min_row
  270. @property
  271. def max_row(self):
  272. """The maximum row index containing data (1-based)
  273. :type: int
  274. """
  275. max_row = 1
  276. if self._cells:
  277. max_row = max(self._cells)[0]
  278. return max_row
  279. @property
  280. def min_column(self):
  281. """The minimum column index containing data (1-based)
  282. :type: int
  283. """
  284. min_col = 1
  285. if self._cells:
  286. min_col = min(c[1] for c in self._cells)
  287. return min_col
  288. @property
  289. def max_column(self):
  290. """The maximum column index containing data (1-based)
  291. :type: int
  292. """
  293. max_col = 1
  294. if self._cells:
  295. max_col = max(c[1] for c in self._cells)
  296. return max_col
  297. def calculate_dimension(self):
  298. """Return the minimum bounding range for all cells containing data (ex. 'A1:M24')
  299. :rtype: string
  300. """
  301. if self._cells:
  302. rows = set()
  303. cols = set()
  304. for row, col in self._cells:
  305. rows.add(row)
  306. cols.add(col)
  307. max_row = max(rows)
  308. max_col = max(cols)
  309. min_col = min(cols)
  310. min_row = min(rows)
  311. else:
  312. return "A1:A1"
  313. return f"{get_column_letter(min_col)}{min_row}:{get_column_letter(max_col)}{max_row}"
  314. @property
  315. def dimensions(self):
  316. """Returns the result of :func:`calculate_dimension`"""
  317. return self.calculate_dimension()
  318. def iter_rows(self, min_row=None, max_row=None, min_col=None, max_col=None, values_only=False):
  319. """
  320. Produces cells from the worksheet, by row. Specify the iteration range
  321. using indices of rows and columns.
  322. If no indices are specified the range starts at A1.
  323. If no cells are in the worksheet an empty tuple will be returned.
  324. :param min_col: smallest column index (1-based index)
  325. :type min_col: int
  326. :param min_row: smallest row index (1-based index)
  327. :type min_row: int
  328. :param max_col: largest column index (1-based index)
  329. :type max_col: int
  330. :param max_row: largest row index (1-based index)
  331. :type max_row: int
  332. :param values_only: whether only cell values should be returned
  333. :type values_only: bool
  334. :rtype: generator
  335. """
  336. if self._current_row == 0 and not any([min_col, min_row, max_col, max_row ]):
  337. return iter(())
  338. min_col = min_col or 1
  339. min_row = min_row or 1
  340. max_col = max_col or self.max_column
  341. max_row = max_row or self.max_row
  342. return self._cells_by_row(min_col, min_row, max_col, max_row, values_only)
  343. def _cells_by_row(self, min_col, min_row, max_col, max_row, values_only=False):
  344. for row in range(min_row, max_row + 1):
  345. cells = (self.cell(row=row, column=column) for column in range(min_col, max_col + 1))
  346. if values_only:
  347. yield tuple(cell.value for cell in cells)
  348. else:
  349. yield tuple(cells)
  350. @property
  351. def rows(self):
  352. """Produces all cells in the worksheet, by row (see :func:`iter_rows`)
  353. :type: generator
  354. """
  355. return self.iter_rows()
  356. @property
  357. def values(self):
  358. """Produces all cell values in the worksheet, by row
  359. :type: generator
  360. """
  361. for row in self.iter_rows(values_only=True):
  362. yield row
  363. def iter_cols(self, min_col=None, max_col=None, min_row=None, max_row=None, values_only=False):
  364. """
  365. Produces cells from the worksheet, by column. Specify the iteration range
  366. using indices of rows and columns.
  367. If no indices are specified the range starts at A1.
  368. If no cells are in the worksheet an empty tuple will be returned.
  369. :param min_col: smallest column index (1-based index)
  370. :type min_col: int
  371. :param min_row: smallest row index (1-based index)
  372. :type min_row: int
  373. :param max_col: largest column index (1-based index)
  374. :type max_col: int
  375. :param max_row: largest row index (1-based index)
  376. :type max_row: int
  377. :param values_only: whether only cell values should be returned
  378. :type values_only: bool
  379. :rtype: generator
  380. """
  381. if self._current_row == 0 and not any([min_col, min_row, max_col, max_row]):
  382. return iter(())
  383. min_col = min_col or 1
  384. min_row = min_row or 1
  385. max_col = max_col or self.max_column
  386. max_row = max_row or self.max_row
  387. return self._cells_by_col(min_col, min_row, max_col, max_row, values_only)
  388. def _cells_by_col(self, min_col, min_row, max_col, max_row, values_only=False):
  389. """
  390. Get cells by column
  391. """
  392. for column in range(min_col, max_col+1):
  393. cells = (self.cell(row=row, column=column)
  394. for row in range(min_row, max_row+1))
  395. if values_only:
  396. yield tuple(cell.value for cell in cells)
  397. else:
  398. yield tuple(cells)
  399. @property
  400. def columns(self):
  401. """Produces all cells in the worksheet, by column (see :func:`iter_cols`)"""
  402. return self.iter_cols()
  403. @property
  404. def column_groups(self):
  405. """
  406. Return a list of column ranges where more than one column
  407. """
  408. return [cd.range for cd in self.column_dimensions.values() if cd.min and cd.max > cd.min]
  409. def set_printer_settings(self, paper_size, orientation):
  410. """Set printer settings """
  411. self.page_setup.paperSize = paper_size
  412. self.page_setup.orientation = orientation
  413. def add_data_validation(self, data_validation):
  414. """ Add a data-validation object to the sheet. The data-validation
  415. object defines the type of data-validation to be applied and the
  416. cell or range of cells it should apply to.
  417. """
  418. self.data_validations.append(data_validation)
  419. def add_chart(self, chart, anchor=None):
  420. """
  421. Add a chart to the sheet
  422. Optionally provide a cell for the top-left anchor
  423. """
  424. if anchor is not None:
  425. chart.anchor = anchor
  426. self._charts.append(chart)
  427. def add_image(self, img, anchor=None):
  428. """
  429. Add an image to the sheet.
  430. Optionally provide a cell for the top-left anchor
  431. """
  432. if anchor is not None:
  433. img.anchor = anchor
  434. self._images.append(img)
  435. def add_table(self, table):
  436. """
  437. Check for duplicate name in definedNames and other worksheet tables
  438. before adding table.
  439. """
  440. if self.parent._duplicate_name(table.name):
  441. raise ValueError("Table with name {0} already exists".format(table.name))
  442. if not hasattr(self, "_get_cell"):
  443. warn("In write-only mode you must add table columns manually")
  444. self._tables.add(table)
  445. @property
  446. def tables(self):
  447. return self._tables
  448. def add_pivot(self, pivot):
  449. self._pivots.append(pivot)
  450. def merge_cells(self, range_string=None, start_row=None, start_column=None, end_row=None, end_column=None):
  451. """ Set merge on a cell range. Range is a cell range (e.g. A1:E1) """
  452. if range_string is None:
  453. cr = CellRange(range_string=range_string, min_col=start_column, min_row=start_row,
  454. max_col=end_column, max_row=end_row)
  455. range_string = cr.coord
  456. mcr = MergedCellRange(self, range_string)
  457. self.merged_cells.add(mcr)
  458. self._clean_merge_range(mcr)
  459. def _clean_merge_range(self, mcr):
  460. """
  461. Remove all but the top left-cell from a range of merged cells
  462. and recreate the lost border information.
  463. Borders are then applied
  464. """
  465. cells = mcr.cells
  466. next(cells) # skip first cell
  467. for row, col in cells:
  468. self._cells[row, col] = MergedCell(self, row, col)
  469. mcr.format()
  470. @property
  471. @deprecated("Use ws.merged_cells.ranges")
  472. def merged_cell_ranges(self):
  473. """Return a copy of cell ranges"""
  474. return self.merged_cells.ranges[:]
  475. def unmerge_cells(self, range_string=None, start_row=None, start_column=None, end_row=None, end_column=None):
  476. """ Remove merge on a cell range. Range is a cell range (e.g. A1:E1) """
  477. cr = CellRange(range_string=range_string, min_col=start_column, min_row=start_row,
  478. max_col=end_column, max_row=end_row)
  479. if cr.coord not in self.merged_cells:
  480. raise ValueError("Cell range {0} is not merged".format(cr.coord))
  481. self.merged_cells.remove(cr)
  482. cells = cr.cells
  483. next(cells) # skip first cell
  484. for row, col in cells:
  485. del self._cells[(row, col)]
  486. def append(self, iterable):
  487. """Appends a group of values at the bottom of the current sheet.
  488. * If it's a list: all values are added in order, starting from the first column
  489. * If it's a dict: values are assigned to the columns indicated by the keys (numbers or letters)
  490. :param iterable: list, range or generator, or dict containing values to append
  491. :type iterable: list|tuple|range|generator or dict
  492. Usage:
  493. * append(['This is A1', 'This is B1', 'This is C1'])
  494. * **or** append({'A' : 'This is A1', 'C' : 'This is C1'})
  495. * **or** append({1 : 'This is A1', 3 : 'This is C1'})
  496. :raise: TypeError when iterable is neither a list/tuple nor a dict
  497. """
  498. row_idx = self._current_row + 1
  499. if (isinstance(iterable, (list, tuple, range))
  500. or isgenerator(iterable)):
  501. for col_idx, content in enumerate(iterable, 1):
  502. if isinstance(content, Cell):
  503. # compatible with write-only mode
  504. cell = content
  505. if cell.parent and cell.parent != self:
  506. raise ValueError("Cells cannot be copied from other worksheets")
  507. cell.parent = self
  508. cell.column = col_idx
  509. cell.row = row_idx
  510. else:
  511. cell = Cell(self, row=row_idx, column=col_idx, value=content)
  512. self._cells[(row_idx, col_idx)] = cell
  513. elif isinstance(iterable, dict):
  514. for col_idx, content in iterable.items():
  515. if isinstance(col_idx, str):
  516. col_idx = column_index_from_string(col_idx)
  517. cell = Cell(self, row=row_idx, column=col_idx, value=content)
  518. self._cells[(row_idx, col_idx)] = cell
  519. else:
  520. self._invalid_row(iterable)
  521. self._current_row = row_idx
  522. def _move_cells(self, min_row=None, min_col=None, offset=0, row_or_col="row"):
  523. """
  524. Move either rows or columns around by the offset
  525. """
  526. reverse = offset > 0 # start at the end if inserting
  527. row_offset = 0
  528. col_offset = 0
  529. # need to make affected ranges contiguous
  530. if row_or_col == 'row':
  531. cells = self.iter_rows(min_row=min_row)
  532. row_offset = offset
  533. key = 0
  534. else:
  535. cells = self.iter_cols(min_col=min_col)
  536. col_offset = offset
  537. key = 1
  538. cells = list(cells)
  539. for row, column in sorted(self._cells, key=itemgetter(key), reverse=reverse):
  540. if min_row and row < min_row:
  541. continue
  542. elif min_col and column < min_col:
  543. continue
  544. self._move_cell(row, column, row_offset, col_offset)
  545. def insert_rows(self, idx, amount=1):
  546. """
  547. Insert row or rows before row==idx
  548. """
  549. self._move_cells(min_row=idx, offset=amount, row_or_col="row")
  550. self._current_row = self.max_row
  551. def insert_cols(self, idx, amount=1):
  552. """
  553. Insert column or columns before col==idx
  554. """
  555. self._move_cells(min_col=idx, offset=amount, row_or_col="column")
  556. def delete_rows(self, idx, amount=1):
  557. """
  558. Delete row or rows from row==idx
  559. """
  560. remainder = _gutter(idx, amount, self.max_row)
  561. self._move_cells(min_row=idx+amount, offset=-amount, row_or_col="row")
  562. # calculating min and max col is an expensive operation, do it only once
  563. min_col = self.min_column
  564. max_col = self.max_column + 1
  565. for row in remainder:
  566. for col in range(min_col, max_col):
  567. if (row, col) in self._cells:
  568. del self._cells[row, col]
  569. self._current_row = self.max_row
  570. if not self._cells:
  571. self._current_row = 0
  572. def delete_cols(self, idx, amount=1):
  573. """
  574. Delete column or columns from col==idx
  575. """
  576. remainder = _gutter(idx, amount, self.max_column)
  577. self._move_cells(min_col=idx+amount, offset=-amount, row_or_col="column")
  578. # calculating min and max row is an expensive operation, do it only once
  579. min_row = self.min_row
  580. max_row = self.max_row + 1
  581. for col in remainder:
  582. for row in range(min_row, max_row):
  583. if (row, col) in self._cells:
  584. del self._cells[row, col]
  585. def move_range(self, cell_range, rows=0, cols=0, translate=False):
  586. """
  587. Move a cell range by the number of rows and/or columns:
  588. down if rows > 0 and up if rows < 0
  589. right if cols > 0 and left if cols < 0
  590. Existing cells will be overwritten.
  591. Formulae and references will not be updated.
  592. """
  593. if isinstance(cell_range, str):
  594. cell_range = CellRange(cell_range)
  595. if not isinstance(cell_range, CellRange):
  596. raise ValueError("Only CellRange objects can be moved")
  597. if not rows and not cols:
  598. return
  599. down = rows > 0
  600. right = cols > 0
  601. if rows:
  602. cells = sorted(cell_range.rows, reverse=down)
  603. else:
  604. cells = sorted(cell_range.cols, reverse=right)
  605. for row, col in chain.from_iterable(cells):
  606. self._move_cell(row, col, rows, cols, translate)
  607. # rebase moved range
  608. cell_range.shift(row_shift=rows, col_shift=cols)
  609. def _move_cell(self, row, column, row_offset, col_offset, translate=False):
  610. """
  611. Move a cell from one place to another.
  612. Delete at old index
  613. Rebase coordinate
  614. """
  615. cell = self._get_cell(row, column)
  616. new_row = cell.row + row_offset
  617. new_col = cell.column + col_offset
  618. self._cells[new_row, new_col] = cell
  619. del self._cells[(cell.row, cell.column)]
  620. cell.row = new_row
  621. cell.column = new_col
  622. if translate and cell.data_type == "f":
  623. t = Translator(cell.value, cell.coordinate)
  624. cell.value = t.translate_formula(row_delta=row_offset, col_delta=col_offset)
  625. def _invalid_row(self, iterable):
  626. raise TypeError('Value must be a list, tuple, range or generator, or a dict. Supplied value is {0}'.format(
  627. type(iterable))
  628. )
  629. def _add_column(self):
  630. """Dimension factory for column information"""
  631. return ColumnDimension(self)
  632. def _add_row(self):
  633. """Dimension factory for row information"""
  634. return RowDimension(self)
  635. @property
  636. def print_title_rows(self):
  637. """Rows to be printed at the top of every page (ex: '1:3')"""
  638. if self._print_rows:
  639. return str(self._print_rows)
  640. @print_title_rows.setter
  641. def print_title_rows(self, rows):
  642. """
  643. Set rows to be printed on the top of every page
  644. format `1:3`
  645. """
  646. if rows is not None:
  647. self._print_rows = RowRange(rows)
  648. @property
  649. def print_title_cols(self):
  650. """Columns to be printed at the left side of every page (ex: 'A:C')"""
  651. if self._print_cols:
  652. return str(self._print_cols)
  653. @print_title_cols.setter
  654. def print_title_cols(self, cols):
  655. """
  656. Set cols to be printed on the left of every page
  657. format ``A:C`
  658. """
  659. if cols is not None:
  660. self._print_cols = ColRange(cols)
  661. @property
  662. def print_titles(self):
  663. titles = PrintTitles(cols=self._print_cols, rows=self._print_rows, title=self.title)
  664. return str(titles)
  665. @property
  666. def print_area(self):
  667. """
  668. The print area for the worksheet, or None if not set. To set, supply a range
  669. like 'A1:D4' or a list of ranges.
  670. """
  671. self._print_area.title = self.title
  672. return str(self._print_area)
  673. @print_area.setter
  674. def print_area(self, value):
  675. """
  676. Range of cells in the form A1:D4 or list of ranges. Print area can be cleared
  677. by passing `None` or an empty list
  678. """
  679. if not value:
  680. self._print_area = PrintArea()
  681. elif isinstance(value, str):
  682. self._print_area = PrintArea.from_string(value)
  683. elif hasattr(value, "__iter__"):
  684. self._print_area = PrintArea.from_string(",".join(value))
  685. def _gutter(idx, offset, max_val):
  686. """
  687. When deleting rows and columns are deleted we rely on overwriting.
  688. This may not be the case for a large offset on small set of cells:
  689. range(cells_to_delete) > range(cell_to_be_moved)
  690. """
  691. gutter = range(max(max_val+1-offset, idx), min(idx+offset, max_val)+1)
  692. return gutter