Updated script that can be controled by Nodejs web app
This commit is contained in:
512
lib/python3.13/site-packages/openpyxl/worksheet/cell_range.py
Normal file
512
lib/python3.13/site-packages/openpyxl/worksheet/cell_range.py
Normal file
@ -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)
|
Reference in New Issue
Block a user