about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/openpyxl/worksheet/worksheet.py
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/openpyxl/worksheet/worksheet.py')
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/worksheet/worksheet.py907
1 files changed, 907 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/worksheet/worksheet.py b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/worksheet.py
new file mode 100644
index 00000000..b7ffbebc
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/worksheet.py
@@ -0,0 +1,907 @@
+# Copyright (c) 2010-2024 openpyxl
+
+"""Worksheet is the 2nd-level container in Excel."""
+
+
+# Python stdlib imports
+from itertools import chain
+from operator import itemgetter
+from inspect import isgenerator
+from warnings import warn
+
+# compatibility imports
+from openpyxl.compat import (
+    deprecated,
+)
+
+# package imports
+from openpyxl.utils import (
+    column_index_from_string,
+    get_column_letter,
+    range_boundaries,
+    coordinate_to_tuple,
+)
+from openpyxl.cell import Cell, MergedCell
+from openpyxl.formatting.formatting import ConditionalFormattingList
+from openpyxl.packaging.relationship import RelationshipList
+from openpyxl.workbook.child import _WorkbookChild
+from openpyxl.workbook.defined_name import (
+    DefinedNameDict,
+)
+
+from openpyxl.formula.translate import Translator
+
+from .datavalidation import DataValidationList
+from .page import (
+    PrintPageSetup,
+    PageMargins,
+    PrintOptions,
+)
+from .dimensions import (
+    ColumnDimension,
+    RowDimension,
+    DimensionHolder,
+    SheetFormatProperties,
+)
+from .protection import SheetProtection
+from .filters import AutoFilter
+from .views import (
+    Pane,
+    Selection,
+    SheetViewList,
+)
+from .cell_range import MultiCellRange, CellRange
+from .merge import MergedCellRange
+from .properties import WorksheetProperties
+from .pagebreak import RowBreak, ColBreak
+from .scenario import ScenarioList
+from .table import TableList
+from .formula import ArrayFormula
+from .print_settings import (
+    PrintTitles,
+    ColRange,
+    RowRange,
+    PrintArea,
+)
+
+
+class Worksheet(_WorkbookChild):
+    """Represents a worksheet.
+
+    Do not create worksheets yourself,
+    use :func:`openpyxl.workbook.Workbook.create_sheet` instead
+
+    """
+
+    _rel_type = "worksheet"
+    _path = "/xl/worksheets/sheet{0}.xml"
+    mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"
+
+    BREAK_NONE = 0
+    BREAK_ROW = 1
+    BREAK_COLUMN = 2
+
+    SHEETSTATE_VISIBLE = 'visible'
+    SHEETSTATE_HIDDEN = 'hidden'
+    SHEETSTATE_VERYHIDDEN = 'veryHidden'
+
+    # Paper size
+    PAPERSIZE_LETTER = '1'
+    PAPERSIZE_LETTER_SMALL = '2'
+    PAPERSIZE_TABLOID = '3'
+    PAPERSIZE_LEDGER = '4'
+    PAPERSIZE_LEGAL = '5'
+    PAPERSIZE_STATEMENT = '6'
+    PAPERSIZE_EXECUTIVE = '7'
+    PAPERSIZE_A3 = '8'
+    PAPERSIZE_A4 = '9'
+    PAPERSIZE_A4_SMALL = '10'
+    PAPERSIZE_A5 = '11'
+
+    # Page orientation
+    ORIENTATION_PORTRAIT = 'portrait'
+    ORIENTATION_LANDSCAPE = 'landscape'
+
+    def __init__(self, parent, title=None):
+        _WorkbookChild.__init__(self, parent, title)
+        self._setup()
+
+    def _setup(self):
+        self.row_dimensions = DimensionHolder(worksheet=self,
+                                              default_factory=self._add_row)
+        self.column_dimensions = DimensionHolder(worksheet=self,
+                                                 default_factory=self._add_column)
+        self.row_breaks = RowBreak()
+        self.col_breaks = ColBreak()
+        self._cells = {}
+        self._charts = []
+        self._images = []
+        self._rels = RelationshipList()
+        self._drawing = None
+        self._comments = []
+        self.merged_cells = MultiCellRange()
+        self._tables = TableList()
+        self._pivots = []
+        self.data_validations = DataValidationList()
+        self._hyperlinks = []
+        self.sheet_state = 'visible'
+        self.page_setup = PrintPageSetup(worksheet=self)
+        self.print_options = PrintOptions()
+        self._print_rows = None
+        self._print_cols = None
+        self._print_area = PrintArea()
+        self.page_margins = PageMargins()
+        self.views = SheetViewList()
+        self.protection = SheetProtection()
+        self.defined_names = DefinedNameDict()
+
+        self._current_row = 0
+        self.auto_filter = AutoFilter()
+        self.conditional_formatting = ConditionalFormattingList()
+        self.legacy_drawing = None
+        self.sheet_properties = WorksheetProperties()
+        self.sheet_format = SheetFormatProperties()
+        self.scenarios = ScenarioList()
+
+
+    @property
+    def sheet_view(self):
+        return self.views.active
+
+
+    @property
+    def selected_cell(self):
+        return self.sheet_view.selection[0].sqref
+
+
+    @property
+    def active_cell(self):
+        return self.sheet_view.selection[0].activeCell
+
+
+    @property
+    def array_formulae(self):
+        """Returns a dictionary of cells with array formulae and the cells in array"""
+        result = {}
+        for c in self._cells.values():
+            if c.data_type == "f":
+                if isinstance(c.value, ArrayFormula):
+                    result[c.coordinate] = c.value.ref
+        return result
+
+
+    @property
+    def show_gridlines(self):
+        return self.sheet_view.showGridLines
+
+
+    @property
+    def freeze_panes(self):
+        if self.sheet_view.pane is not None:
+            return self.sheet_view.pane.topLeftCell
+
+
+    @freeze_panes.setter
+    def freeze_panes(self, topLeftCell=None):
+        if isinstance(topLeftCell, Cell):
+            topLeftCell = topLeftCell.coordinate
+        if topLeftCell == 'A1':
+            topLeftCell = None
+
+        if not topLeftCell:
+            self.sheet_view.pane = None
+            return
+
+        row, column = coordinate_to_tuple(topLeftCell)
+
+        view = self.sheet_view
+        view.pane = Pane(topLeftCell=topLeftCell,
+                        activePane="topRight",
+                        state="frozen")
+        view.selection[0].pane = "topRight"
+
+        if column > 1:
+            view.pane.xSplit = column - 1
+        if row > 1:
+            view.pane.ySplit = row - 1
+            view.pane.activePane = 'bottomLeft'
+            view.selection[0].pane = "bottomLeft"
+            if column > 1:
+                view.selection[0].pane = "bottomRight"
+                view.pane.activePane = 'bottomRight'
+
+        if row > 1 and column > 1:
+            sel = list(view.selection)
+            sel.insert(0, Selection(pane="topRight", activeCell=None, sqref=None))
+            sel.insert(1, Selection(pane="bottomLeft", activeCell=None, sqref=None))
+            view.selection = sel
+
+
+    def cell(self, row, column, value=None):
+        """
+        Returns a cell object based on the given coordinates.
+
+        Usage: cell(row=15, column=1, value=5)
+
+        Calling `cell` creates cells in memory when they
+        are first accessed.
+
+        :param row: row index of the cell (e.g. 4)
+        :type row: int
+
+        :param column: column index of the cell (e.g. 3)
+        :type column: int
+
+        :param value: value of the cell (e.g. 5)
+        :type value: numeric or time or string or bool or none
+
+        :rtype: openpyxl.cell.cell.Cell
+        """
+
+        if row < 1 or column < 1:
+            raise ValueError("Row or column values must be at least 1")
+
+        cell = self._get_cell(row, column)
+        if value is not None:
+            cell.value = value
+
+        return cell
+
+
+    def _get_cell(self, row, column):
+        """
+        Internal method for getting a cell from a worksheet.
+        Will create a new cell if one doesn't already exist.
+        """
+        if not 0 < row < 1048577:
+            raise ValueError(f"Row numbers must be between 1 and 1048576. Row number supplied was {row}")
+        coordinate = (row, column)
+        if not coordinate in self._cells:
+            cell = Cell(self, row=row, column=column)
+            self._add_cell(cell)
+        return self._cells[coordinate]
+
+
+    def _add_cell(self, cell):
+        """
+        Internal method for adding cell objects.
+        """
+        column = cell.col_idx
+        row = cell.row
+        self._current_row = max(row, self._current_row)
+        self._cells[(row, column)] = cell
+
+
+    def __getitem__(self, key):
+        """Convenience access by Excel style coordinates
+
+        The key can be a single cell coordinate 'A1', a range of cells 'A1:D25',
+        individual rows or columns 'A', 4 or ranges of rows or columns 'A:D',
+        4:10.
+
+        Single cells will always be created if they do not exist.
+
+        Returns either a single cell or a tuple of rows or columns.
+        """
+        if isinstance(key, slice):
+            if not all([key.start, key.stop]):
+                raise IndexError("{0} is not a valid coordinate or range".format(key))
+            key = "{0}:{1}".format(key.start, key.stop)
+
+        if isinstance(key, int):
+            key = str(key
+                      )
+        min_col, min_row, max_col, max_row = range_boundaries(key)
+
+        if not any([min_col, min_row, max_col, max_row]):
+            raise IndexError("{0} is not a valid coordinate or range".format(key))
+
+        if min_row is None:
+            cols = tuple(self.iter_cols(min_col, max_col))
+            if min_col == max_col:
+                cols = cols[0]
+            return cols
+        if min_col is None:
+            rows = tuple(self.iter_rows(min_col=min_col, min_row=min_row,
+                                        max_col=self.max_column, max_row=max_row))
+            if min_row == max_row:
+                rows = rows[0]
+            return rows
+        if ":" not in key:
+            return self._get_cell(min_row, min_col)
+        return tuple(self.iter_rows(min_row=min_row, min_col=min_col,
+                                    max_row=max_row, max_col=max_col))
+
+
+    def __setitem__(self, key, value):
+        self[key].value = value
+
+
+    def __iter__(self):
+        return self.iter_rows()
+
+
+    def __delitem__(self, key):
+        row, column = coordinate_to_tuple(key)
+        if (row, column) in self._cells:
+            del self._cells[(row, column)]
+
+
+    @property
+    def min_row(self):
+        """The minimum row index containing data (1-based)
+
+        :type: int
+        """
+        min_row = 1
+        if self._cells:
+            min_row = min(self._cells)[0]
+        return min_row
+
+
+    @property
+    def max_row(self):
+        """The maximum row index containing data (1-based)
+
+        :type: int
+        """
+        max_row = 1
+        if self._cells:
+            max_row = max(self._cells)[0]
+        return max_row
+
+
+    @property
+    def min_column(self):
+        """The minimum column index containing data (1-based)
+
+        :type: int
+        """
+        min_col = 1
+        if self._cells:
+            min_col = min(c[1] for c in self._cells)
+        return min_col
+
+
+    @property
+    def max_column(self):
+        """The maximum column index containing data (1-based)
+
+        :type: int
+        """
+        max_col = 1
+        if self._cells:
+            max_col = max(c[1] for c in self._cells)
+        return max_col
+
+
+    def calculate_dimension(self):
+        """Return the minimum bounding range for all cells containing data (ex. 'A1:M24')
+
+        :rtype: string
+        """
+        if self._cells:
+            rows = set()
+            cols = set()
+            for row, col in self._cells:
+                rows.add(row)
+                cols.add(col)
+            max_row = max(rows)
+            max_col = max(cols)
+            min_col = min(cols)
+            min_row = min(rows)
+        else:
+            return "A1:A1"
+
+        return f"{get_column_letter(min_col)}{min_row}:{get_column_letter(max_col)}{max_row}"
+
+
+    @property
+    def dimensions(self):
+        """Returns the result of :func:`calculate_dimension`"""
+        return self.calculate_dimension()
+
+
+    def iter_rows(self, min_row=None, max_row=None, min_col=None, max_col=None, values_only=False):
+        """
+        Produces cells from the worksheet, by row. Specify the iteration range
+        using indices of rows and columns.
+
+        If no indices are specified the range starts at A1.
+
+        If no cells are in the worksheet an empty tuple will be returned.
+
+        :param min_col: smallest column index (1-based index)
+        :type min_col: int
+
+        :param min_row: smallest row index (1-based index)
+        :type min_row: int
+
+        :param max_col: largest column index (1-based index)
+        :type max_col: int
+
+        :param max_row: largest row index (1-based index)
+        :type max_row: int
+
+        :param values_only: whether only cell values should be returned
+        :type values_only: bool
+
+        :rtype: generator
+        """
+
+        if self._current_row == 0 and not any([min_col, min_row, max_col, max_row ]):
+            return iter(())
+
+
+        min_col = min_col or 1
+        min_row = min_row or 1
+        max_col = max_col or self.max_column
+        max_row = max_row or self.max_row
+
+        return self._cells_by_row(min_col, min_row, max_col, max_row, values_only)
+
+
+    def _cells_by_row(self, min_col, min_row, max_col, max_row, values_only=False):
+        for row in range(min_row, max_row + 1):
+            cells = (self.cell(row=row, column=column) for column in range(min_col, max_col + 1))
+            if values_only:
+                yield tuple(cell.value for cell in cells)
+            else:
+                yield tuple(cells)
+
+
+    @property
+    def rows(self):
+        """Produces all cells in the worksheet, by row (see :func:`iter_rows`)
+
+        :type: generator
+        """
+        return self.iter_rows()
+
+
+    @property
+    def values(self):
+        """Produces all cell values in the worksheet, by row
+
+        :type: generator
+        """
+        for row in self.iter_rows(values_only=True):
+            yield row
+
+
+    def iter_cols(self, min_col=None, max_col=None, min_row=None, max_row=None, values_only=False):
+        """
+        Produces cells from the worksheet, by column. Specify the iteration range
+        using indices of rows and columns.
+
+        If no indices are specified the range starts at A1.
+
+        If no cells are in the worksheet an empty tuple will be returned.
+
+        :param min_col: smallest column index (1-based index)
+        :type min_col: int
+
+        :param min_row: smallest row index (1-based index)
+        :type min_row: int
+
+        :param max_col: largest column index (1-based index)
+        :type max_col: int
+
+        :param max_row: largest row index (1-based index)
+        :type max_row: int
+
+        :param values_only: whether only cell values should be returned
+        :type values_only: bool
+
+        :rtype: generator
+        """
+
+        if self._current_row == 0 and not any([min_col, min_row, max_col, max_row]):
+            return iter(())
+
+        min_col = min_col or 1
+        min_row = min_row or 1
+        max_col = max_col or self.max_column
+        max_row = max_row or self.max_row
+
+        return self._cells_by_col(min_col, min_row, max_col, max_row, values_only)
+
+
+    def _cells_by_col(self, min_col, min_row, max_col, max_row, values_only=False):
+        """
+        Get cells by column
+        """
+        for column in range(min_col, max_col+1):
+            cells = (self.cell(row=row, column=column)
+                        for row in range(min_row, max_row+1))
+            if values_only:
+                yield tuple(cell.value for cell in cells)
+            else:
+                yield tuple(cells)
+
+
+    @property
+    def columns(self):
+        """Produces all cells in the worksheet, by column  (see :func:`iter_cols`)"""
+        return self.iter_cols()
+
+
+    @property
+    def column_groups(self):
+        """
+        Return a list of column ranges where more than one column
+        """
+        return [cd.range for cd in self.column_dimensions.values() if cd.min and cd.max > cd.min]
+
+
+    def set_printer_settings(self, paper_size, orientation):
+        """Set printer settings """
+
+        self.page_setup.paperSize = paper_size
+        self.page_setup.orientation = orientation
+
+
+    def add_data_validation(self, data_validation):
+        """ Add a data-validation object to the sheet.  The data-validation
+            object defines the type of data-validation to be applied and the
+            cell or range of cells it should apply to.
+        """
+        self.data_validations.append(data_validation)
+
+
+    def add_chart(self, chart, anchor=None):
+        """
+        Add a chart to the sheet
+        Optionally provide a cell for the top-left anchor
+        """
+        if anchor is not None:
+            chart.anchor = anchor
+        self._charts.append(chart)
+
+
+    def add_image(self, img, anchor=None):
+        """
+        Add an image to the sheet.
+        Optionally provide a cell for the top-left anchor
+        """
+        if anchor is not None:
+            img.anchor = anchor
+        self._images.append(img)
+
+
+    def add_table(self, table):
+        """
+        Check for duplicate name in definedNames and other worksheet tables
+        before adding table.
+        """
+
+        if self.parent._duplicate_name(table.name):
+            raise ValueError("Table with name {0} already exists".format(table.name))
+        if not hasattr(self, "_get_cell"):
+            warn("In write-only mode you must add table columns manually")
+        self._tables.add(table)
+
+
+    @property
+    def tables(self):
+        return self._tables
+
+
+    def add_pivot(self, pivot):
+        self._pivots.append(pivot)
+
+
+    def merge_cells(self, range_string=None, start_row=None, start_column=None, end_row=None, end_column=None):
+        """ Set merge on a cell range.  Range is a cell range (e.g. A1:E1) """
+        if range_string is None:
+            cr = CellRange(range_string=range_string, min_col=start_column, min_row=start_row,
+                      max_col=end_column, max_row=end_row)
+            range_string = cr.coord
+        mcr = MergedCellRange(self, range_string)
+        self.merged_cells.add(mcr)
+        self._clean_merge_range(mcr)
+
+
+    def _clean_merge_range(self, mcr):
+        """
+        Remove all but the top left-cell from a range of merged cells
+        and recreate the lost border information.
+        Borders are then applied
+        """
+        cells = mcr.cells
+        next(cells) # skip first cell
+        for row, col in cells:
+            self._cells[row, col] = MergedCell(self, row, col)
+        mcr.format()
+
+
+    @property
+    @deprecated("Use ws.merged_cells.ranges")
+    def merged_cell_ranges(self):
+        """Return a copy of cell ranges"""
+        return self.merged_cells.ranges[:]
+
+
+    def unmerge_cells(self, range_string=None, start_row=None, start_column=None, end_row=None, end_column=None):
+        """ Remove merge on a cell range.  Range is a cell range (e.g. A1:E1) """
+        cr = CellRange(range_string=range_string, min_col=start_column, min_row=start_row,
+                      max_col=end_column, max_row=end_row)
+
+        if cr.coord not in self.merged_cells:
+            raise ValueError("Cell range {0} is not merged".format(cr.coord))
+
+        self.merged_cells.remove(cr)
+
+        cells = cr.cells
+        next(cells) # skip first cell
+        for row, col in cells:
+            del self._cells[(row, col)]
+
+
+    def append(self, iterable):
+        """Appends a group of values at the bottom of the current sheet.
+
+        * If it's a list: all values are added in order, starting from the first column
+        * If it's a dict: values are assigned to the columns indicated by the keys (numbers or letters)
+
+        :param iterable: list, range or generator, or dict containing values to append
+        :type iterable: list|tuple|range|generator or dict
+
+        Usage:
+
+        * append(['This is A1', 'This is B1', 'This is C1'])
+        * **or** append({'A' : 'This is A1', 'C' : 'This is C1'})
+        * **or** append({1 : 'This is A1', 3 : 'This is C1'})
+
+        :raise: TypeError when iterable is neither a list/tuple nor a dict
+
+        """
+        row_idx = self._current_row + 1
+
+        if (isinstance(iterable, (list, tuple, range))
+            or isgenerator(iterable)):
+            for col_idx, content in enumerate(iterable, 1):
+                if isinstance(content, Cell):
+                    # compatible with write-only mode
+                    cell = content
+                    if cell.parent and cell.parent != self:
+                        raise ValueError("Cells cannot be copied from other worksheets")
+                    cell.parent = self
+                    cell.column = col_idx
+                    cell.row = row_idx
+                else:
+                    cell = Cell(self, row=row_idx, column=col_idx, value=content)
+                self._cells[(row_idx, col_idx)] = cell
+
+        elif isinstance(iterable, dict):
+            for col_idx, content in iterable.items():
+                if isinstance(col_idx, str):
+                    col_idx = column_index_from_string(col_idx)
+                cell = Cell(self, row=row_idx, column=col_idx, value=content)
+                self._cells[(row_idx, col_idx)] = cell
+
+        else:
+            self._invalid_row(iterable)
+
+        self._current_row = row_idx
+
+
+    def _move_cells(self, min_row=None, min_col=None, offset=0, row_or_col="row"):
+        """
+        Move either rows or columns around by the offset
+        """
+        reverse = offset > 0 # start at the end if inserting
+        row_offset = 0
+        col_offset = 0
+
+        # need to make affected ranges contiguous
+        if row_or_col == 'row':
+            cells = self.iter_rows(min_row=min_row)
+            row_offset = offset
+            key = 0
+        else:
+            cells = self.iter_cols(min_col=min_col)
+            col_offset = offset
+            key = 1
+        cells = list(cells)
+
+        for row, column in sorted(self._cells, key=itemgetter(key), reverse=reverse):
+            if min_row and row < min_row:
+                continue
+            elif min_col and column < min_col:
+                continue
+
+            self._move_cell(row, column, row_offset, col_offset)
+
+
+    def insert_rows(self, idx, amount=1):
+        """
+        Insert row or rows before row==idx
+        """
+        self._move_cells(min_row=idx, offset=amount, row_or_col="row")
+        self._current_row = self.max_row
+
+
+    def insert_cols(self, idx, amount=1):
+        """
+        Insert column or columns before col==idx
+        """
+        self._move_cells(min_col=idx, offset=amount, row_or_col="column")
+
+
+    def delete_rows(self, idx, amount=1):
+        """
+        Delete row or rows from row==idx
+        """
+
+        remainder = _gutter(idx, amount, self.max_row)
+
+        self._move_cells(min_row=idx+amount, offset=-amount, row_or_col="row")
+
+        # calculating min and max col is an expensive operation, do it only once
+        min_col = self.min_column
+        max_col = self.max_column + 1
+        for row in remainder:
+            for col in range(min_col, max_col):
+                if (row, col) in self._cells:
+                    del self._cells[row, col]
+        self._current_row = self.max_row
+        if not self._cells:
+            self._current_row = 0
+
+
+    def delete_cols(self, idx, amount=1):
+        """
+        Delete column or columns from col==idx
+        """
+
+        remainder = _gutter(idx, amount, self.max_column)
+
+        self._move_cells(min_col=idx+amount, offset=-amount, row_or_col="column")
+
+        # calculating min and max row is an expensive operation, do it only once
+        min_row = self.min_row
+        max_row = self.max_row + 1
+        for col in remainder:
+            for row in range(min_row, max_row):
+                if (row, col) in self._cells:
+                    del self._cells[row, col]
+
+
+    def move_range(self, cell_range, rows=0, cols=0, translate=False):
+        """
+        Move a cell range by the number of rows and/or columns:
+        down if rows > 0 and up if rows < 0
+        right if cols > 0 and left if cols < 0
+        Existing cells will be overwritten.
+        Formulae and references will not be updated.
+        """
+        if isinstance(cell_range, str):
+            cell_range = CellRange(cell_range)
+        if not isinstance(cell_range, CellRange):
+            raise ValueError("Only CellRange objects can be moved")
+        if not rows and not cols:
+            return
+
+        down = rows > 0
+        right = cols > 0
+
+        if rows:
+            cells = sorted(cell_range.rows, reverse=down)
+        else:
+            cells = sorted(cell_range.cols, reverse=right)
+
+        for row, col in chain.from_iterable(cells):
+            self._move_cell(row, col, rows, cols, translate)
+
+        # rebase moved range
+        cell_range.shift(row_shift=rows, col_shift=cols)
+
+
+    def _move_cell(self, row, column, row_offset, col_offset, translate=False):
+        """
+        Move a cell from one place to another.
+        Delete at old index
+        Rebase coordinate
+        """
+        cell = self._get_cell(row, column)
+        new_row = cell.row + row_offset
+        new_col = cell.column + col_offset
+        self._cells[new_row, new_col] = cell
+        del self._cells[(cell.row, cell.column)]
+        cell.row = new_row
+        cell.column = new_col
+        if translate and cell.data_type == "f":
+            t = Translator(cell.value, cell.coordinate)
+            cell.value = t.translate_formula(row_delta=row_offset, col_delta=col_offset)
+
+
+    def _invalid_row(self, iterable):
+        raise TypeError('Value must be a list, tuple, range or generator, or a dict. Supplied value is {0}'.format(
+            type(iterable))
+                        )
+
+
+    def _add_column(self):
+        """Dimension factory for column information"""
+
+        return ColumnDimension(self)
+
+    def _add_row(self):
+        """Dimension factory for row information"""
+
+        return RowDimension(self)
+
+
+    @property
+    def print_title_rows(self):
+        """Rows to be printed at the top of every page (ex: '1:3')"""
+        if self._print_rows:
+            return str(self._print_rows)
+
+
+    @print_title_rows.setter
+    def print_title_rows(self, rows):
+        """
+        Set rows to be printed on the top of every page
+        format `1:3`
+        """
+        if rows is not None:
+            self._print_rows = RowRange(rows)
+
+
+    @property
+    def print_title_cols(self):
+        """Columns to be printed at the left side of every page (ex: 'A:C')"""
+        if self._print_cols:
+            return str(self._print_cols)
+
+
+    @print_title_cols.setter
+    def print_title_cols(self, cols):
+        """
+        Set cols to be printed on the left of every page
+        format ``A:C`
+        """
+        if cols is not None:
+            self._print_cols = ColRange(cols)
+
+
+    @property
+    def print_titles(self):
+        titles = PrintTitles(cols=self._print_cols, rows=self._print_rows, title=self.title)
+        return str(titles)
+
+
+    @property
+    def print_area(self):
+        """
+        The print area for the worksheet, or None if not set. To set, supply a range
+        like 'A1:D4' or a list of ranges.
+        """
+        self._print_area.title = self.title
+        return str(self._print_area)
+
+
+    @print_area.setter
+    def print_area(self, value):
+        """
+        Range of cells in the form A1:D4 or list of ranges. Print area can be cleared
+        by passing `None` or an empty list
+        """
+        if not value:
+            self._print_area = PrintArea()
+        elif isinstance(value, str):
+            self._print_area = PrintArea.from_string(value)
+        elif hasattr(value, "__iter__"):
+            self._print_area = PrintArea.from_string(",".join(value))
+
+
+def _gutter(idx, offset, max_val):
+    """
+    When deleting rows and columns are deleted we rely on overwriting.
+    This may not be the case for a large offset on small set of cells:
+    range(cells_to_delete) > range(cell_to_be_moved)
+    """
+    gutter = range(max(max_val+1-offset, idx), min(idx+offset, max_val)+1)
+    return gutter