aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/openpyxl/worksheet/cell_range.py
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/openpyxl/worksheet/cell_range.py')
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/worksheet/cell_range.py512
1 files changed, 512 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/worksheet/cell_range.py b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/cell_range.py
new file mode 100644
index 00000000..2fbf5e22
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/cell_range.py
@@ -0,0 +1,512 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from copy import copy
+from operator import attrgetter
+
+from openpyxl.descriptors import Strict
+from openpyxl.descriptors import MinMax
+from openpyxl.descriptors.sequence import UniqueSequence
+from openpyxl.descriptors.serialisable import Serialisable
+
+from openpyxl.utils import (
+ range_boundaries,
+ range_to_tuple,
+ get_column_letter,
+ quote_sheetname,
+)
+
+class CellRange(Serialisable):
+ """
+ Represents a range in a sheet: title and coordinates.
+
+ This object is used to perform operations on ranges, like:
+
+ - shift, expand or shrink
+ - union/intersection with another sheet range,
+
+ We can check whether a range is:
+
+ - equal or not equal to another,
+ - disjoint of another,
+ - contained in another.
+
+ We can get:
+
+ - the size of a range.
+ - the range bounds (vertices)
+ - the coordinates,
+ - the string representation,
+
+ """
+
+ min_col = MinMax(min=1, max=18278, expected_type=int)
+ min_row = MinMax(min=1, max=1048576, expected_type=int)
+ max_col = MinMax(min=1, max=18278, expected_type=int)
+ max_row = MinMax(min=1, max=1048576, expected_type=int)
+
+
+ def __init__(self, range_string=None, min_col=None, min_row=None,
+ max_col=None, max_row=None, title=None):
+ if range_string is not None:
+ if "!" in range_string:
+ title, (min_col, min_row, max_col, max_row) = range_to_tuple(range_string)
+ else:
+ min_col, min_row, max_col, max_row = range_boundaries(range_string)
+
+ self.min_col = min_col
+ self.min_row = min_row
+ self.max_col = max_col
+ self.max_row = max_row
+ self.title = title
+
+ if min_col > max_col:
+ fmt = "{max_col} must be greater than {min_col}"
+ raise ValueError(fmt.format(min_col=min_col, max_col=max_col))
+ if min_row > max_row:
+ fmt = "{max_row} must be greater than {min_row}"
+ raise ValueError(fmt.format(min_row=min_row, max_row=max_row))
+
+
+ @property
+ def bounds(self):
+ """
+ Vertices of the range as a tuple
+ """
+ return self.min_col, self.min_row, self.max_col, self.max_row
+
+
+ @property
+ def coord(self):
+ """
+ Excel-style representation of the range
+ """
+ fmt = "{min_col}{min_row}:{max_col}{max_row}"
+ if (self.min_col == self.max_col
+ and self.min_row == self.max_row):
+ fmt = "{min_col}{min_row}"
+
+ return fmt.format(
+ min_col=get_column_letter(self.min_col),
+ min_row=self.min_row,
+ max_col=get_column_letter(self.max_col),
+ max_row=self.max_row
+ )
+
+ @property
+ def rows(self):
+ """
+ Return cell coordinates as rows
+ """
+ for row in range(self.min_row, self.max_row+1):
+ yield [(row, col) for col in range(self.min_col, self.max_col+1)]
+
+
+ @property
+ def cols(self):
+ """
+ Return cell coordinates as columns
+ """
+ for col in range(self.min_col, self.max_col+1):
+ yield [(row, col) for row in range(self.min_row, self.max_row+1)]
+
+
+ @property
+ def cells(self):
+ from itertools import product
+ return product(range(self.min_row, self.max_row+1), range(self.min_col, self.max_col+1))
+
+
+ def _check_title(self, other):
+ """
+ Check whether comparisons between ranges are possible.
+ Cannot compare ranges from different worksheets
+ Skip if the range passed in has no title.
+ """
+ if not isinstance(other, CellRange):
+ raise TypeError(repr(type(other)))
+
+ if other.title and self.title != other.title:
+ raise ValueError("Cannot work with ranges from different worksheets")
+
+
+ def __repr__(self):
+ fmt = u"<{cls} {coord}>"
+ if self.title:
+ fmt = u"<{cls} {title!r}!{coord}>"
+ return fmt.format(cls=self.__class__.__name__, title=self.title, coord=self.coord)
+
+
+ def __hash__(self):
+ return hash((self.min_row, self.min_col, self.max_row, self.max_col))
+
+
+ def __str__(self):
+ fmt = "{coord}"
+ title = self.title
+ if title:
+ fmt = u"{title}!{coord}"
+ title = quote_sheetname(title)
+ return fmt.format(title=title, coord=self.coord)
+
+
+ def __copy__(self):
+ return self.__class__(min_col=self.min_col, min_row=self.min_row,
+ max_col=self.max_col, max_row=self.max_row,
+ title=self.title)
+
+
+ def shift(self, col_shift=0, row_shift=0):
+ """
+ Shift the focus of the range according to the shift values (*col_shift*, *row_shift*).
+
+ :type col_shift: int
+ :param col_shift: number of columns to be moved by, can be negative
+ :type row_shift: int
+ :param row_shift: number of rows to be moved by, can be negative
+ :raise: :class:`ValueError` if any row or column index < 1
+ """
+
+ if (self.min_col + col_shift <= 0
+ or self.min_row + row_shift <= 0):
+ raise ValueError("Invalid shift value: col_shift={0}, row_shift={1}".format(col_shift, row_shift))
+ self.min_col += col_shift
+ self.min_row += row_shift
+ self.max_col += col_shift
+ self.max_row += row_shift
+
+
+ def __ne__(self, other):
+ """
+ Test whether the ranges are not equal.
+
+ :type other: openpyxl.worksheet.cell_range.CellRange
+ :param other: Other sheet range
+ :return: ``True`` if *range* != *other*.
+ """
+ try:
+ self._check_title(other)
+ except ValueError:
+ return True
+
+ return (
+ other.min_row != self.min_row
+ or self.max_row != other.max_row
+ or other.min_col != self.min_col
+ or self.max_col != other.max_col
+ )
+
+
+ def __eq__(self, other):
+ """
+ Test whether the ranges are equal.
+
+ :type other: openpyxl.worksheet.cell_range.CellRange
+ :param other: Other sheet range
+ :return: ``True`` if *range* == *other*.
+ """
+ return not self.__ne__(other)
+
+
+ def issubset(self, other):
+ """
+ Test whether every cell in this range is also in *other*.
+
+ :type other: openpyxl.worksheet.cell_range.CellRange
+ :param other: Other sheet range
+ :return: ``True`` if *range* <= *other*.
+ """
+ self._check_title(other)
+
+ return other.__superset(self)
+
+ __le__ = issubset
+
+
+ def __lt__(self, other):
+ """
+ Test whether *other* contains every cell of this range, and more.
+
+ :type other: openpyxl.worksheet.cell_range.CellRange
+ :param other: Other sheet range
+ :return: ``True`` if *range* < *other*.
+ """
+ return self.__le__(other) and self.__ne__(other)
+
+
+ def __superset(self, other):
+ return (
+ (self.min_row <= other.min_row <= other.max_row <= self.max_row)
+ and
+ (self.min_col <= other.min_col <= other.max_col <= self.max_col)
+ )
+
+
+ def issuperset(self, other):
+ """
+ Test whether every cell in *other* is in this range.
+
+ :type other: openpyxl.worksheet.cell_range.CellRange
+ :param other: Other sheet range
+ :return: ``True`` if *range* >= *other* (or *other* in *range*).
+ """
+ self._check_title(other)
+
+ return self.__superset(other)
+
+ __ge__ = issuperset
+
+
+ def __contains__(self, coord):
+ """
+ Check whether the range contains a particular cell coordinate
+ """
+ cr = self.__class__(coord)
+ return self.__superset(cr)
+
+
+ def __gt__(self, other):
+ """
+ Test whether this range contains every cell in *other*, and more.
+
+ :type other: openpyxl.worksheet.cell_range.CellRange
+ :param other: Other sheet range
+ :return: ``True`` if *range* > *other*.
+ """
+ return self.__ge__(other) and self.__ne__(other)
+
+
+ def isdisjoint(self, other):
+ """
+ Return ``True`` if this range has no cell in common with *other*.
+ Ranges are disjoint if and only if their intersection is the empty range.
+
+ :type other: openpyxl.worksheet.cell_range.CellRange
+ :param other: Other sheet range.
+ :return: ``True`` if the range has no cells in common with other.
+ """
+ self._check_title(other)
+
+ # Sort by top-left vertex
+ if self.bounds > other.bounds:
+ self, other = other, self
+
+ return (self.max_col < other.min_col
+ or self.max_row < other.min_row
+ or other.max_row < self.min_row)
+
+
+ def intersection(self, other):
+ """
+ Return a new range with cells common to this range and *other*
+
+ :type other: openpyxl.worksheet.cell_range.CellRange
+ :param other: Other sheet range.
+ :return: the intersecting sheet range.
+ :raise: :class:`ValueError` if the *other* range doesn't intersect
+ with this range.
+ """
+ if self.isdisjoint(other):
+ raise ValueError("Range {0} doesn't intersect {0}".format(self, other))
+
+ min_row = max(self.min_row, other.min_row)
+ max_row = min(self.max_row, other.max_row)
+ min_col = max(self.min_col, other.min_col)
+ max_col = min(self.max_col, other.max_col)
+
+ return CellRange(min_col=min_col, min_row=min_row, max_col=max_col,
+ max_row=max_row)
+
+ __and__ = intersection
+
+
+ def union(self, other):
+ """
+ Return the minimal superset of this range and *other*. This new range
+ will contain all cells from this range, *other*, and any additional
+ cells required to form a rectangular ``CellRange``.
+
+ :type other: openpyxl.worksheet.cell_range.CellRange
+ :param other: Other sheet range.
+ :return: a ``CellRange`` that is a superset of this and *other*.
+ """
+ self._check_title(other)
+
+ min_row = min(self.min_row, other.min_row)
+ max_row = max(self.max_row, other.max_row)
+ min_col = min(self.min_col, other.min_col)
+ max_col = max(self.max_col, other.max_col)
+ return CellRange(min_col=min_col, min_row=min_row, max_col=max_col,
+ max_row=max_row, title=self.title)
+
+ __or__ = union
+
+
+ def __iter__(self):
+ """
+ For use as a dictionary elsewhere in the library.
+ """
+ for x in self.__attrs__:
+ if x == "title":
+ continue
+ v = getattr(self, x)
+ yield x, v
+
+
+ def expand(self, right=0, down=0, left=0, up=0):
+ """
+ Expand the range by the dimensions provided.
+
+ :type right: int
+ :param right: expand range to the right by this number of cells
+ :type down: int
+ :param down: expand range down by this number of cells
+ :type left: int
+ :param left: expand range to the left by this number of cells
+ :type up: int
+ :param up: expand range up by this number of cells
+ """
+ self.min_col -= left
+ self.min_row -= up
+ self.max_col += right
+ self.max_row += down
+
+
+ def shrink(self, right=0, bottom=0, left=0, top=0):
+ """
+ Shrink the range by the dimensions provided.
+
+ :type right: int
+ :param right: shrink range from the right by this number of cells
+ :type down: int
+ :param down: shrink range from the top by this number of cells
+ :type left: int
+ :param left: shrink range from the left by this number of cells
+ :type up: int
+ :param up: shrink range from the bottom by this number of cells
+ """
+ self.min_col += left
+ self.min_row += top
+ self.max_col -= right
+ self.max_row -= bottom
+
+
+ @property
+ def size(self):
+ """ Return the size of the range as a dictionary of rows and columns. """
+ cols = self.max_col + 1 - self.min_col
+ rows = self.max_row + 1 - self.min_row
+ return {'columns':cols, 'rows':rows}
+
+
+ @property
+ def top(self):
+ """A list of cell coordinates that comprise the top of the range"""
+ return [(self.min_row, col) for col in range(self.min_col, self.max_col+1)]
+
+
+ @property
+ def bottom(self):
+ """A list of cell coordinates that comprise the bottom of the range"""
+ return [(self.max_row, col) for col in range(self.min_col, self.max_col+1)]
+
+
+ @property
+ def left(self):
+ """A list of cell coordinates that comprise the left-side of the range"""
+ return [(row, self.min_col) for row in range(self.min_row, self.max_row+1)]
+
+
+ @property
+ def right(self):
+ """A list of cell coordinates that comprise the right-side of the range"""
+ return [(row, self.max_col) for row in range(self.min_row, self.max_row+1)]
+
+
+class MultiCellRange(Strict):
+
+
+ ranges = UniqueSequence(expected_type=CellRange)
+
+
+ def __init__(self, ranges=set()):
+ if isinstance(ranges, str):
+ ranges = [CellRange(r) for r in ranges.split()]
+ self.ranges = set(ranges)
+
+
+ def __contains__(self, coord):
+ if isinstance(coord, str):
+ coord = CellRange(coord)
+ for r in self.ranges:
+ if coord <= r:
+ return True
+ return False
+
+
+ def __repr__(self):
+ ranges = " ".join([str(r) for r in self.sorted()])
+ return f"<{self.__class__.__name__} [{ranges}]>"
+
+
+ def __str__(self):
+ ranges = u" ".join([str(r) for r in self.sorted()])
+ return ranges
+
+
+ def __hash__(self):
+ return hash(str(self))
+
+
+ def sorted(self):
+ """
+ Return a sorted list of items
+ """
+ return sorted(self.ranges, key=attrgetter('min_col', 'min_row', 'max_col', 'max_row'))
+
+
+ def add(self, coord):
+ """
+ Add a cell coordinate or CellRange
+ """
+ cr = coord
+ if isinstance(coord, str):
+ cr = CellRange(coord)
+ elif not isinstance(coord, CellRange):
+ raise ValueError("You can only add CellRanges")
+ if cr not in self:
+ self.ranges.add(cr)
+
+
+ def __iadd__(self, coord):
+ self.add(coord)
+ return self
+
+
+ def __eq__(self, other):
+ if isinstance(other, str):
+ other = self.__class__(other)
+ return self.ranges == other.ranges
+
+
+ def __ne__(self, other):
+ return not self == other
+
+
+ def __bool__(self):
+ return bool(self.ranges)
+
+
+ def remove(self, coord):
+ if not isinstance(coord, CellRange):
+ coord = CellRange(coord)
+ self.ranges.remove(coord)
+
+
+ def __iter__(self):
+ for cr in self.ranges:
+ yield cr
+
+
+ def __copy__(self):
+ ranges = {copy(r) for r in self.ranges}
+ return MultiCellRange(ranges)