Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/openpyxl/worksheet/worksheet.py: 22%
454 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1# Copyright (c) 2010-2022 openpyxl
3"""Worksheet is the 2nd-level container in Excel."""
6# Python stdlib imports
7from itertools import chain
8from operator import itemgetter
9from inspect import isgenerator
10from warnings import warn
12# compatibility imports
13from openpyxl.compat import (
14 deprecated,
15)
17# package imports
18from openpyxl.utils import (
19 column_index_from_string,
20 get_column_letter,
21 range_boundaries,
22 coordinate_to_tuple,
23 absolute_coordinate,
24)
25from openpyxl.cell import Cell, MergedCell
26from openpyxl.formatting.formatting import ConditionalFormattingList
27from openpyxl.packaging.relationship import RelationshipList
28from openpyxl.workbook.child import _WorkbookChild
29from openpyxl.workbook.defined_name import COL_RANGE_RE, ROW_RANGE_RE
30from openpyxl.formula.translate import Translator
32from .datavalidation import DataValidationList
33from .page import (
34 PrintPageSetup,
35 PageMargins,
36 PrintOptions,
37)
38from .dimensions import (
39 ColumnDimension,
40 RowDimension,
41 DimensionHolder,
42 SheetFormatProperties,
43)
44from .protection import SheetProtection
45from .filters import AutoFilter
46from .views import (
47 Pane,
48 Selection,
49 SheetViewList,
50)
51from .cell_range import MultiCellRange, CellRange
52from .merge import MergedCellRange
53from .properties import WorksheetProperties
54from .pagebreak import RowBreak, ColBreak
55from .scenario import ScenarioList
56from .table import TableList
59class Worksheet(_WorkbookChild):
60 """Represents a worksheet.
62 Do not create worksheets yourself,
63 use :func:`openpyxl.workbook.Workbook.create_sheet` instead
65 """
67 _rel_type = "worksheet"
68 _path = "/xl/worksheets/sheet{0}.xml"
69 mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"
71 BREAK_NONE = 0
72 BREAK_ROW = 1
73 BREAK_COLUMN = 2
75 SHEETSTATE_VISIBLE = 'visible'
76 SHEETSTATE_HIDDEN = 'hidden'
77 SHEETSTATE_VERYHIDDEN = 'veryHidden'
79 # Paper size
80 PAPERSIZE_LETTER = '1'
81 PAPERSIZE_LETTER_SMALL = '2'
82 PAPERSIZE_TABLOID = '3'
83 PAPERSIZE_LEDGER = '4'
84 PAPERSIZE_LEGAL = '5'
85 PAPERSIZE_STATEMENT = '6'
86 PAPERSIZE_EXECUTIVE = '7'
87 PAPERSIZE_A3 = '8'
88 PAPERSIZE_A4 = '9'
89 PAPERSIZE_A4_SMALL = '10'
90 PAPERSIZE_A5 = '11'
92 # Page orientation
93 ORIENTATION_PORTRAIT = 'portrait'
94 ORIENTATION_LANDSCAPE = 'landscape'
96 def __init__(self, parent, title=None):
97 _WorkbookChild.__init__(self, parent, title)
98 self._setup()
100 def _setup(self):
101 self.row_dimensions = DimensionHolder(worksheet=self,
102 default_factory=self._add_row)
103 self.column_dimensions = DimensionHolder(worksheet=self,
104 default_factory=self._add_column)
105 self.row_breaks = RowBreak()
106 self.col_breaks = ColBreak()
107 self._cells = {}
108 self._charts = []
109 self._images = []
110 self._rels = RelationshipList()
111 self._drawing = None
112 self._comments = []
113 self.merged_cells = MultiCellRange()
114 self._tables = TableList()
115 self._pivots = []
116 self.data_validations = DataValidationList()
117 self._hyperlinks = []
118 self.sheet_state = 'visible'
119 self.page_setup = PrintPageSetup(worksheet=self)
120 self.print_options = PrintOptions()
121 self._print_rows = None
122 self._print_cols = None
123 self._print_area = None
124 self.page_margins = PageMargins()
125 self.views = SheetViewList()
126 self.protection = SheetProtection()
128 self._current_row = 0
129 self.auto_filter = AutoFilter()
130 self.paper_size = None
131 self.formula_attributes = {}
132 self.orientation = None
133 self.conditional_formatting = ConditionalFormattingList()
134 self.legacy_drawing = None
135 self.sheet_properties = WorksheetProperties()
136 self.sheet_format = SheetFormatProperties()
137 self.scenarios = ScenarioList()
140 @property
141 def sheet_view(self):
142 return self.views.sheetView[0]
145 @property
146 def selected_cell(self):
147 return self.sheet_view.selection[0].sqref
150 @property
151 def active_cell(self):
152 return self.sheet_view.selection[0].activeCell
155 @property
156 def page_breaks(self):
157 return (self.row_breaks, self.col_breaks) # legacy, remove at some point
160 @property
161 def show_gridlines(self):
162 return self.sheet_view.showGridLines
165 """ To keep compatibility with previous versions"""
166 @property
167 def show_summary_below(self):
168 return self.sheet_properties.outlinePr.summaryBelow
170 @property
171 def show_summary_right(self):
172 return self.sheet_properties.outlinePr.summaryRight
175 @property
176 def freeze_panes(self):
177 if self.sheet_view.pane is not None:
178 return self.sheet_view.pane.topLeftCell
180 @freeze_panes.setter
181 def freeze_panes(self, topLeftCell=None):
182 if isinstance(topLeftCell, Cell):
183 topLeftCell = topLeftCell.coordinate
184 if topLeftCell == 'A1':
185 topLeftCell = None
187 if not topLeftCell:
188 self.sheet_view.pane = None
189 return
191 row, column = coordinate_to_tuple(topLeftCell)
193 view = self.sheet_view
194 view.pane = Pane(topLeftCell=topLeftCell,
195 activePane="topRight",
196 state="frozen")
197 view.selection[0].pane = "topRight"
199 if column > 1:
200 view.pane.xSplit = column - 1
201 if row > 1:
202 view.pane.ySplit = row - 1
203 view.pane.activePane = 'bottomLeft'
204 view.selection[0].pane = "bottomLeft"
205 if column > 1:
206 view.selection[0].pane = "bottomRight"
207 view.pane.activePane = 'bottomRight'
209 if row > 1 and column > 1:
210 sel = list(view.selection)
211 sel.insert(0, Selection(pane="topRight", activeCell=None, sqref=None))
212 sel.insert(1, Selection(pane="bottomLeft", activeCell=None, sqref=None))
213 view.selection = sel
216 def cell(self, row, column, value=None):
217 """
218 Returns a cell object based on the given coordinates.
220 Usage: cell(row=15, column=1, value=5)
222 Calling `cell` creates cells in memory when they
223 are first accessed.
225 :param row: row index of the cell (e.g. 4)
226 :type row: int
228 :param column: column index of the cell (e.g. 3)
229 :type column: int
231 :param value: value of the cell (e.g. 5)
232 :type value: numeric or time or string or bool or none
234 :rtype: openpyxl.cell.cell.Cell
235 """
237 if row < 1 or column < 1:
238 raise ValueError("Row or column values must be at least 1")
240 cell = self._get_cell(row, column)
241 if value is not None:
242 cell.value = value
244 return cell
247 def _get_cell(self, row, column):
248 """
249 Internal method for getting a cell from a worksheet.
250 Will create a new cell if one doesn't already exist.
251 """
252 if not 0 < row < 1048577:
253 raise ValueError("Row numbers must be between 1 and 1048576")
254 coordinate = (row, column)
255 if not coordinate in self._cells:
256 cell = Cell(self, row=row, column=column)
257 self._add_cell(cell)
258 return self._cells[coordinate]
261 def _add_cell(self, cell):
262 """
263 Internal method for adding cell objects.
264 """
265 column = cell.col_idx
266 row = cell.row
267 self._current_row = max(row, self._current_row)
268 self._cells[(row, column)] = cell
271 def __getitem__(self, key):
272 """Convenience access by Excel style coordinates
274 The key can be a single cell coordinate 'A1', a range of cells 'A1:D25',
275 individual rows or columns 'A', 4 or ranges of rows or columns 'A:D',
276 4:10.
278 Single cells will always be created if they do not exist.
280 Returns either a single cell or a tuple of rows or columns.
281 """
282 if isinstance(key, slice):
283 if not all([key.start, key.stop]):
284 raise IndexError("{0} is not a valid coordinate or range".format(key))
285 key = "{0}:{1}".format(key.start, key.stop)
287 if isinstance(key, int):
288 key = str(key
289 )
290 min_col, min_row, max_col, max_row = range_boundaries(key)
292 if not any([min_col, min_row, max_col, max_row]):
293 raise IndexError("{0} is not a valid coordinate or range".format(key))
295 if min_row is None:
296 cols = tuple(self.iter_cols(min_col, max_col))
297 if min_col == max_col:
298 cols = cols[0]
299 return cols
300 if min_col is None:
301 rows = tuple(self.iter_rows(min_col=min_col, min_row=min_row,
302 max_col=self.max_column, max_row=max_row))
303 if min_row == max_row:
304 rows = rows[0]
305 return rows
306 if ":" not in key:
307 return self._get_cell(min_row, min_col)
308 return tuple(self.iter_rows(min_row=min_row, min_col=min_col,
309 max_row=max_row, max_col=max_col))
312 def __setitem__(self, key, value):
313 self[key].value = value
316 def __iter__(self):
317 return self.iter_rows()
320 def __delitem__(self, key):
321 row, column = coordinate_to_tuple(key)
322 if (row, column) in self._cells:
323 del self._cells[(row, column)]
326 @property
327 def min_row(self):
328 """The minimium row index containing data (1-based)
330 :type: int
331 """
332 min_row = 1
333 if self._cells:
334 rows = set(c[0] for c in self._cells)
335 min_row = min(rows)
336 return min_row
339 @property
340 def max_row(self):
341 """The maximum row index containing data (1-based)
343 :type: int
344 """
345 max_row = 1
346 if self._cells:
347 rows = set(c[0] for c in self._cells)
348 max_row = max(rows)
349 return max_row
352 @property
353 def min_column(self):
354 """The minimum column index containing data (1-based)
356 :type: int
357 """
358 min_col = 1
359 if self._cells:
360 cols = set(c[1] for c in self._cells)
361 min_col = min(cols)
362 return min_col
365 @property
366 def max_column(self):
367 """The maximum column index containing data (1-based)
369 :type: int
370 """
371 max_col = 1
372 if self._cells:
373 cols = set(c[1] for c in self._cells)
374 max_col = max(cols)
375 return max_col
378 def calculate_dimension(self):
379 """Return the minimum bounding range for all cells containing data (ex. 'A1:M24')
381 :rtype: string
382 """
383 if self._cells:
384 rows = set()
385 cols = set()
386 for row, col in self._cells:
387 rows.add(row)
388 cols.add(col)
389 max_row = max(rows)
390 max_col = max(cols)
391 min_col = min(cols)
392 min_row = min(rows)
393 else:
394 return "A1:A1"
396 return f"{get_column_letter(min_col)}{min_row}:{get_column_letter(max_col)}{max_row}"
399 @property
400 def dimensions(self):
401 """Returns the result of :func:`calculate_dimension`"""
402 return self.calculate_dimension()
405 def iter_rows(self, min_row=None, max_row=None, min_col=None, max_col=None, values_only=False):
406 """
407 Produces cells from the worksheet, by row. Specify the iteration range
408 using indices of rows and columns.
410 If no indices are specified the range starts at A1.
412 If no cells are in the worksheet an empty tuple will be returned.
414 :param min_col: smallest column index (1-based index)
415 :type min_col: int
417 :param min_row: smallest row index (1-based index)
418 :type min_row: int
420 :param max_col: largest column index (1-based index)
421 :type max_col: int
423 :param max_row: largest row index (1-based index)
424 :type max_row: int
426 :param values_only: whether only cell values should be returned
427 :type values_only: bool
429 :rtype: generator
430 """
432 if self._current_row == 0 and not any([min_col, min_row, max_col, max_row ]):
433 return iter(())
436 min_col = min_col or 1
437 min_row = min_row or 1
438 max_col = max_col or self.max_column
439 max_row = max_row or self.max_row
441 return self._cells_by_row(min_col, min_row, max_col, max_row, values_only)
444 def _cells_by_row(self, min_col, min_row, max_col, max_row, values_only=False):
445 for row in range(min_row, max_row + 1):
446 cells = (self.cell(row=row, column=column) for column in range(min_col, max_col + 1))
447 if values_only:
448 yield tuple(cell.value for cell in cells)
449 else:
450 yield tuple(cells)
453 @property
454 def rows(self):
455 """Produces all cells in the worksheet, by row (see :func:`iter_rows`)
457 :type: generator
458 """
459 return self.iter_rows()
462 @property
463 def values(self):
464 """Produces all cell values in the worksheet, by row
466 :type: generator
467 """
468 for row in self.iter_rows(values_only=True):
469 yield row
472 def iter_cols(self, min_col=None, max_col=None, min_row=None, max_row=None, values_only=False):
473 """
474 Produces cells from the worksheet, by column. Specify the iteration range
475 using indices of rows and columns.
477 If no indices are specified the range starts at A1.
479 If no cells are in the worksheet an empty tuple will be returned.
481 :param min_col: smallest column index (1-based index)
482 :type min_col: int
484 :param min_row: smallest row index (1-based index)
485 :type min_row: int
487 :param max_col: largest column index (1-based index)
488 :type max_col: int
490 :param max_row: largest row index (1-based index)
491 :type max_row: int
493 :param values_only: whether only cell values should be returned
494 :type values_only: bool
496 :rtype: generator
497 """
499 if self._current_row == 0 and not any([min_col, min_row, max_col, max_row]):
500 return iter(())
502 min_col = min_col or 1
503 min_row = min_row or 1
504 max_col = max_col or self.max_column
505 max_row = max_row or self.max_row
507 return self._cells_by_col(min_col, min_row, max_col, max_row, values_only)
510 def _cells_by_col(self, min_col, min_row, max_col, max_row, values_only=False):
511 """
512 Get cells by column
513 """
514 for column in range(min_col, max_col+1):
515 cells = (self.cell(row=row, column=column)
516 for row in range(min_row, max_row+1))
517 if values_only:
518 yield tuple(cell.value for cell in cells)
519 else:
520 yield tuple(cells)
523 @property
524 def columns(self):
525 """Produces all cells in the worksheet, by column (see :func:`iter_cols`)"""
526 return self.iter_cols()
529 def set_printer_settings(self, paper_size, orientation):
530 """Set printer settings """
532 self.page_setup.paperSize = paper_size
533 self.page_setup.orientation = orientation
536 def add_data_validation(self, data_validation):
537 """ Add a data-validation object to the sheet. The data-validation
538 object defines the type of data-validation to be applied and the
539 cell or range of cells it should apply to.
540 """
541 self.data_validations.append(data_validation)
544 def add_chart(self, chart, anchor=None):
545 """
546 Add a chart to the sheet
547 Optionally provide a cell for the top-left anchor
548 """
549 if anchor is not None:
550 chart.anchor = anchor
551 self._charts.append(chart)
554 def add_image(self, img, anchor=None):
555 """
556 Add an image to the sheet.
557 Optionally provide a cell for the top-left anchor
558 """
559 if anchor is not None:
560 img.anchor = anchor
561 self._images.append(img)
564 def add_table(self, table):
565 """
566 Check for duplicate name in definedNames and other worksheet tables
567 before adding table.
568 """
570 if self.parent._duplicate_name(table.name):
571 raise ValueError("Table with name {0} already exists".format(table.name))
572 if not hasattr(self, "_get_cell"):
573 warn("In write-only mode you must add table columns manually")
574 self._tables.add(table)
577 @property
578 def tables(self):
579 return self._tables
582 def add_pivot(self, pivot):
583 self._pivots.append(pivot)
586 def merge_cells(self, range_string=None, start_row=None, start_column=None, end_row=None, end_column=None):
587 """ Set merge on a cell range. Range is a cell range (e.g. A1:E1) """
588 if range_string is None:
589 cr = CellRange(range_string=range_string, min_col=start_column, min_row=start_row,
590 max_col=end_column, max_row=end_row)
591 range_string = cr.coord
592 mcr = MergedCellRange(self, range_string)
593 self.merged_cells.add(mcr)
594 self._clean_merge_range(mcr)
597 def _clean_merge_range(self, mcr):
598 """
599 Remove all but the top left-cell from a range of merged cells
600 and recreate the lost border information.
601 Borders are then applied
602 """
603 cells = mcr.cells
604 next(cells) # skip first cell
605 for row, col in cells:
606 self._cells[row, col] = MergedCell(self, row, col)
607 mcr.format()
610 @property
611 @deprecated("Use ws.merged_cells.ranges")
612 def merged_cell_ranges(self):
613 """Return a copy of cell ranges"""
614 return self.merged_cells.ranges[:]
617 def unmerge_cells(self, range_string=None, start_row=None, start_column=None, end_row=None, end_column=None):
618 """ Remove merge on a cell range. Range is a cell range (e.g. A1:E1) """
619 cr = CellRange(range_string=range_string, min_col=start_column, min_row=start_row,
620 max_col=end_column, max_row=end_row)
622 if cr.coord not in self.merged_cells:
623 raise ValueError("Cell range {0} is not merged".format(cr.coord))
625 self.merged_cells.remove(cr)
627 cells = cr.cells
628 next(cells) # skip first cell
629 for row, col in cells:
630 del self._cells[(row, col)]
633 def append(self, iterable):
634 """Appends a group of values at the bottom of the current sheet.
636 * If it's a list: all values are added in order, starting from the first column
637 * If it's a dict: values are assigned to the columns indicated by the keys (numbers or letters)
639 :param iterable: list, range or generator, or dict containing values to append
640 :type iterable: list|tuple|range|generator or dict
642 Usage:
644 * append(['This is A1', 'This is B1', 'This is C1'])
645 * **or** append({'A' : 'This is A1', 'C' : 'This is C1'})
646 * **or** append({1 : 'This is A1', 3 : 'This is C1'})
648 :raise: TypeError when iterable is neither a list/tuple nor a dict
650 """
651 row_idx = self._current_row + 1
653 if (isinstance(iterable, (list, tuple, range))
654 or isgenerator(iterable)):
655 for col_idx, content in enumerate(iterable, 1):
656 if isinstance(content, Cell):
657 # compatible with write-only mode
658 cell = content
659 if cell.parent and cell.parent != self:
660 raise ValueError("Cells cannot be copied from other worksheets")
661 cell.parent = self
662 cell.column = col_idx
663 cell.row = row_idx
664 else:
665 cell = Cell(self, row=row_idx, column=col_idx, value=content)
666 self._cells[(row_idx, col_idx)] = cell
668 elif isinstance(iterable, dict):
669 for col_idx, content in iterable.items():
670 if isinstance(col_idx, str):
671 col_idx = column_index_from_string(col_idx)
672 cell = Cell(self, row=row_idx, column=col_idx, value=content)
673 self._cells[(row_idx, col_idx)] = cell
675 else:
676 self._invalid_row(iterable)
678 self._current_row = row_idx
681 def _move_cells(self, min_row=None, min_col=None, offset=0, row_or_col="row"):
682 """
683 Move either rows or columns around by the offset
684 """
685 reverse = offset > 0 # start at the end if inserting
686 row_offset = 0
687 col_offset = 0
689 # need to make affected ranges contiguous
690 if row_or_col == 'row':
691 cells = self.iter_rows(min_row=min_row)
692 row_offset = offset
693 key = 0
694 else:
695 cells = self.iter_cols(min_col=min_col)
696 col_offset = offset
697 key = 1
698 cells = list(cells)
700 for row, column in sorted(self._cells, key=itemgetter(key), reverse=reverse):
701 if min_row and row < min_row:
702 continue
703 elif min_col and column < min_col:
704 continue
706 self._move_cell(row, column, row_offset, col_offset)
709 def insert_rows(self, idx, amount=1):
710 """
711 Insert row or rows before row==idx
712 """
713 self._move_cells(min_row=idx, offset=amount, row_or_col="row")
714 self._current_row = self.max_row
717 def insert_cols(self, idx, amount=1):
718 """
719 Insert column or columns before col==idx
720 """
721 self._move_cells(min_col=idx, offset=amount, row_or_col="column")
724 def delete_rows(self, idx, amount=1):
725 """
726 Delete row or rows from row==idx
727 """
729 remainder = _gutter(idx, amount, self.max_row)
731 self._move_cells(min_row=idx+amount, offset=-amount, row_or_col="row")
733 # calculating min and max col is an expensive operation, do it only once
734 min_col = self.min_column
735 max_col = self.max_column + 1
736 for row in remainder:
737 for col in range(min_col, max_col):
738 if (row, col) in self._cells:
739 del self._cells[row, col]
740 self._current_row = self.max_row
741 if not self._cells:
742 self._current_row = 0
745 def delete_cols(self, idx, amount=1):
746 """
747 Delete column or columns from col==idx
748 """
750 remainder = _gutter(idx, amount, self.max_column)
752 self._move_cells(min_col=idx+amount, offset=-amount, row_or_col="column")
754 # calculating min and max row is an expensive operation, do it only once
755 min_row = self.min_row
756 max_row = self.max_row + 1
757 for col in remainder:
758 for row in range(min_row, max_row):
759 if (row, col) in self._cells:
760 del self._cells[row, col]
763 def move_range(self, cell_range, rows=0, cols=0, translate=False):
764 """
765 Move a cell range by the number of rows and/or columns:
766 down if rows > 0 and up if rows < 0
767 right if cols > 0 and left if cols < 0
768 Existing cells will be overwritten.
769 Formulae and references will not be updated.
770 """
771 if isinstance(cell_range, str):
772 cell_range = CellRange(cell_range)
773 if not isinstance(cell_range, CellRange):
774 raise ValueError("Only CellRange objects can be moved")
775 if not rows and not cols:
776 return
778 down = rows > 0
779 right = cols > 0
781 if rows:
782 cells = sorted(cell_range.rows, reverse=down)
783 else:
784 cells = sorted(cell_range.cols, reverse=right)
786 for row, col in chain.from_iterable(cells):
787 self._move_cell(row, col, rows, cols, translate)
789 # rebase moved range
790 cell_range.shift(row_shift=rows, col_shift=cols)
793 def _move_cell(self, row, column, row_offset, col_offset, translate=False):
794 """
795 Move a cell from one place to another.
796 Delete at old index
797 Rebase coordinate
798 """
799 cell = self._get_cell(row, column)
800 new_row = cell.row + row_offset
801 new_col = cell.column + col_offset
802 self._cells[new_row, new_col] = cell
803 del self._cells[(cell.row, cell.column)]
804 cell.row = new_row
805 cell.column = new_col
806 if translate and cell.data_type == "f":
807 t = Translator(cell.value, cell.coordinate)
808 cell.value = t.translate_formula(row_delta=row_offset, col_delta=col_offset)
811 def _invalid_row(self, iterable):
812 raise TypeError('Value must be a list, tuple, range or generator, or a dict. Supplied value is {0}'.format(
813 type(iterable))
814 )
817 def _add_column(self):
818 """Dimension factory for column information"""
820 return ColumnDimension(self)
822 def _add_row(self):
823 """Dimension factory for row information"""
825 return RowDimension(self)
828 @property
829 def print_title_rows(self):
830 """Rows to be printed at the top of every page (ex: '1:3')"""
831 if self._print_rows:
832 return self._print_rows
835 @print_title_rows.setter
836 def print_title_rows(self, rows):
837 """
838 Set rows to be printed on the top of every page
839 format `1:3`
840 """
841 if rows is not None:
842 if not ROW_RANGE_RE.match(rows):
843 raise ValueError("Print title rows must be the form 1:3")
844 self._print_rows = rows
847 @property
848 def print_title_cols(self):
849 """Columns to be printed at the left side of every page (ex: 'A:C')"""
850 if self._print_cols:
851 return self._print_cols
854 @print_title_cols.setter
855 def print_title_cols(self, cols):
856 """
857 Set cols to be printed on the left of every page
858 format ``A:C`
859 """
860 if cols is not None:
861 if not COL_RANGE_RE.match(cols):
862 raise ValueError("Print title cols must be the form C:D")
863 self._print_cols = cols
866 @property
867 def print_titles(self):
868 if self.print_title_cols and self.print_title_rows:
869 return ",".join([self.print_title_rows, self.print_title_cols])
870 else:
871 return self.print_title_rows or self.print_title_cols
874 @property
875 def print_area(self):
876 """
877 The print area for the worksheet, or None if not set. To set, supply a range
878 like 'A1:D4' or a list of ranges.
879 """
880 return self._print_area
883 @print_area.setter
884 def print_area(self, value):
885 """
886 Range of cells in the form A1:D4 or list of ranges
887 """
888 if isinstance(value, str):
889 value = [value]
891 self._print_area = [absolute_coordinate(v) for v in value]
894def _gutter(idx, offset, max_val):
895 """
896 When deleting rows and columns are deleted we rely on overwriting.
897 This may not be the case for a large offset on small set of cells:
898 range(cells_to_delete) > range(cell_to_be_moved)
899 """
900 gutter = range(max(max_val+1-offset, idx), min(idx+offset, max_val)+1)
901 return gutter