Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/openpyxl/worksheet/cell_range.py: 26%
193 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
3from copy import copy
5from openpyxl.descriptors import Strict
6from openpyxl.descriptors import MinMax, Sequence
7from openpyxl.descriptors.serialisable import Serialisable
9from openpyxl.utils import (
10 range_boundaries,
11 range_to_tuple,
12 get_column_letter,
13 quote_sheetname,
14)
17class CellRange(Serialisable):
18 """
19 Represents a range in a sheet: title and coordinates.
21 This object is used to perform operations on ranges, like:
23 - shift, expand or shrink
24 - union/intersection with another sheet range,
26 We can check whether a range is:
28 - equal or not equal to another,
29 - disjoint of another,
30 - contained in another.
32 We can get:
34 - the size of a range.
35 - the range bounds (vertices)
36 - the coordinates,
37 - the string representation,
39 """
41 min_col = MinMax(min=1, max=18278, expected_type=int)
42 min_row = MinMax(min=1, max=1048576, expected_type=int)
43 max_col = MinMax(min=1, max=18278, expected_type=int)
44 max_row = MinMax(min=1, max=1048576, expected_type=int)
47 def __init__(self, range_string=None, min_col=None, min_row=None,
48 max_col=None, max_row=None, title=None):
49 if range_string is not None:
50 if "!" in range_string:
51 title, (min_col, min_row, max_col, max_row) = range_to_tuple(range_string)
52 else:
53 min_col, min_row, max_col, max_row = range_boundaries(range_string)
55 self.min_col = min_col
56 self.min_row = min_row
57 self.max_col = max_col
58 self.max_row = max_row
59 self.title = title
61 if min_col > max_col:
62 fmt = "{max_col} must be greater than {min_col}"
63 raise ValueError(fmt.format(min_col=min_col, max_col=max_col))
64 if min_row > max_row:
65 fmt = "{max_row} must be greater than {min_row}"
66 raise ValueError(fmt.format(min_row=min_row, max_row=max_row))
69 @property
70 def bounds(self):
71 """
72 Vertices of the range as a tuple
73 """
74 return self.min_col, self.min_row, self.max_col, self.max_row
77 @property
78 def coord(self):
79 """
80 Excel-style representation of the range
81 """
82 fmt = "{min_col}{min_row}:{max_col}{max_row}"
83 if (self.min_col == self.max_col
84 and self.min_row == self.max_row):
85 fmt = "{min_col}{min_row}"
87 return fmt.format(
88 min_col=get_column_letter(self.min_col),
89 min_row=self.min_row,
90 max_col=get_column_letter(self.max_col),
91 max_row=self.max_row
92 )
94 @property
95 def rows(self):
96 """
97 Return cell coordinates as rows
98 """
99 for row in range(self.min_row, self.max_row+1):
100 yield [(row, col) for col in range(self.min_col, self.max_col+1)]
103 @property
104 def cols(self):
105 """
106 Return cell coordinates as columns
107 """
108 for col in range(self.min_col, self.max_col+1):
109 yield [(row, col) for row in range(self.min_row, self.max_row+1)]
112 @property
113 def cells(self):
114 from itertools import product
115 return product(range(self.min_row, self.max_row+1), range(self.min_col, self.max_col+1))
118 def _check_title(self, other):
119 """
120 Check whether comparisons between ranges are possible.
121 Cannot compare ranges from different worksheets
122 Skip if the range passed in has no title.
123 """
124 if not isinstance(other, CellRange):
125 raise TypeError(repr(type(other)))
127 if other.title and self.title != other.title:
128 raise ValueError("Cannot work with ranges from different worksheets")
131 def __repr__(self):
132 fmt = u"<{cls} {coord}>"
133 if self.title:
134 fmt = u"<{cls} {title!r}!{coord}>"
135 return fmt.format(cls=self.__class__.__name__, title=self.title, coord=self.coord)
138 def __str__(self):
139 fmt = "{coord}"
140 title = self.title
141 if title:
142 fmt = u"{title}!{coord}"
143 title = quote_sheetname(title)
144 return fmt.format(title=title, coord=self.coord)
147 def __copy__(self):
148 return self.__class__(min_col=self.min_col, min_row=self.min_row,
149 max_col=self.max_col, max_row=self.max_row,
150 title=self.title)
153 def shift(self, col_shift=0, row_shift=0):
154 """
155 Shift the focus of the range according to the shift values (*col_shift*, *row_shift*).
157 :type col_shift: int
158 :param col_shift: number of columns to be moved by, can be negative
159 :type row_shift: int
160 :param row_shift: number of rows to be moved by, can be negative
161 :raise: :class:`ValueError` if any row or column index < 1
162 """
164 if (self.min_col + col_shift <= 0
165 or self.min_row + row_shift <= 0):
166 raise ValueError("Invalid shift value: col_shift={0}, row_shift={1}".format(col_shift, row_shift))
167 self.min_col += col_shift
168 self.min_row += row_shift
169 self.max_col += col_shift
170 self.max_row += row_shift
173 def __ne__(self, other):
174 """
175 Test whether the ranges are not equal.
177 :type other: openpyxl.worksheet.cell_range.CellRange
178 :param other: Other sheet range
179 :return: ``True`` if *range* != *other*.
180 """
181 try:
182 self._check_title(other)
183 except ValueError:
184 return True
186 return (
187 other.min_row != self.min_row
188 or self.max_row != other.max_row
189 or other.min_col != self.min_col
190 or self.max_col != other.max_col
191 )
194 def __eq__(self, other):
195 """
196 Test whether the ranges are equal.
198 :type other: openpyxl.worksheet.cell_range.CellRange
199 :param other: Other sheet range
200 :return: ``True`` if *range* == *other*.
201 """
202 return not self.__ne__(other)
205 def issubset(self, other):
206 """
207 Test whether every cell in this range is also in *other*.
209 :type other: openpyxl.worksheet.cell_range.CellRange
210 :param other: Other sheet range
211 :return: ``True`` if *range* <= *other*.
212 """
213 self._check_title(other)
215 return other.__superset(self)
217 __le__ = issubset
220 def __lt__(self, other):
221 """
222 Test whether *other* contains every cell of this range, and more.
224 :type other: openpyxl.worksheet.cell_range.CellRange
225 :param other: Other sheet range
226 :return: ``True`` if *range* < *other*.
227 """
228 return self.__le__(other) and self.__ne__(other)
231 def __superset(self, other):
232 return (
233 (self.min_row <= other.min_row <= other.max_row <= self.max_row)
234 and
235 (self.min_col <= other.min_col <= other.max_col <= self.max_col)
236 )
239 def issuperset(self, other):
240 """
241 Test whether every cell in *other* is in this range.
243 :type other: openpyxl.worksheet.cell_range.CellRange
244 :param other: Other sheet range
245 :return: ``True`` if *range* >= *other* (or *other* in *range*).
246 """
247 self._check_title(other)
249 return self.__superset(other)
251 __ge__ = issuperset
254 def __contains__(self, coord):
255 """
256 Check whether the range contains a particular cell coordinate
257 """
258 cr = self.__class__(coord)
259 return self.__superset(cr)
262 def __gt__(self, other):
263 """
264 Test whether this range contains every cell in *other*, and more.
266 :type other: openpyxl.worksheet.cell_range.CellRange
267 :param other: Other sheet range
268 :return: ``True`` if *range* > *other*.
269 """
270 return self.__ge__(other) and self.__ne__(other)
273 def isdisjoint(self, other):
274 """
275 Return ``True`` if this range has no cell in common with *other*.
276 Ranges are disjoint if and only if their intersection is the empty range.
278 :type other: openpyxl.worksheet.cell_range.CellRange
279 :param other: Other sheet range.
280 :return: ``True`` if the range has no cells in common with other.
281 """
282 self._check_title(other)
284 # Sort by top-left vertex
285 if self.bounds > other.bounds:
286 self, other = other, self
288 return (self.max_col < other.min_col
289 or self.max_row < other.min_row
290 or other.max_row < self.min_row)
293 def intersection(self, other):
294 """
295 Return a new range with cells common to this range and *other*
297 :type other: openpyxl.worksheet.cell_range.CellRange
298 :param other: Other sheet range.
299 :return: the intersecting sheet range.
300 :raise: :class:`ValueError` if the *other* range doesn't intersect
301 with this range.
302 """
303 if self.isdisjoint(other):
304 raise ValueError("Range {0} doesn't intersect {0}".format(self, other))
306 min_row = max(self.min_row, other.min_row)
307 max_row = min(self.max_row, other.max_row)
308 min_col = max(self.min_col, other.min_col)
309 max_col = min(self.max_col, other.max_col)
311 return CellRange(min_col=min_col, min_row=min_row, max_col=max_col,
312 max_row=max_row)
314 __and__ = intersection
317 def union(self, other):
318 """
319 Return the minimal superset of this range and *other*. This new range
320 will contain all cells from this range, *other*, and any additional
321 cells required to form a rectangular ``CellRange``.
323 :type other: openpyxl.worksheet.cell_range.CellRange
324 :param other: Other sheet range.
325 :return: a ``CellRange`` that is a superset of this and *other*.
326 """
327 self._check_title(other)
329 min_row = min(self.min_row, other.min_row)
330 max_row = max(self.max_row, other.max_row)
331 min_col = min(self.min_col, other.min_col)
332 max_col = max(self.max_col, other.max_col)
333 return CellRange(min_col=min_col, min_row=min_row, max_col=max_col,
334 max_row=max_row, title=self.title)
336 __or__ = union
339 def __iter__(self):
340 """
341 For use as a dictionary elsewhere in the library.
342 """
343 for x in self.__attrs__:
344 if x == "title":
345 continue
346 v = getattr(self, x)
347 yield x, v
350 def expand(self, right=0, down=0, left=0, up=0):
351 """
352 Expand the range by the dimensions provided.
354 :type right: int
355 :param right: expand range to the right by this number of cells
356 :type down: int
357 :param down: expand range down by this number of cells
358 :type left: int
359 :param left: expand range to the left by this number of cells
360 :type up: int
361 :param up: expand range up by this number of cells
362 """
363 self.min_col -= left
364 self.min_row -= up
365 self.max_col += right
366 self.max_row += down
369 def shrink(self, right=0, bottom=0, left=0, top=0):
370 """
371 Shrink the range by the dimensions provided.
373 :type right: int
374 :param right: shrink range from the right by this number of cells
375 :type down: int
376 :param down: shrink range from the top by this number of cells
377 :type left: int
378 :param left: shrink range from the left by this number of cells
379 :type up: int
380 :param up: shrink range from the bottown by this number of cells
381 """
382 self.min_col += left
383 self.min_row += top
384 self.max_col -= right
385 self.max_row -= bottom
388 @property
389 def size(self):
390 """ Return the size of the range as a dictionary of rows and columns. """
391 cols = self.max_col + 1 - self.min_col
392 rows = self.max_row + 1 - self.min_row
393 return {'columns':cols, 'rows':rows}
396 @property
397 def top(self):
398 """A list of cell coordinates that comprise the top of the range"""
399 return [(self.min_row, col) for col in range(self.min_col, self.max_col+1)]
402 @property
403 def bottom(self):
404 """A list of cell coordinates that comprise the bottom of the range"""
405 return [(self.max_row, col) for col in range(self.min_col, self.max_col+1)]
408 @property
409 def left(self):
410 """A list of cell coordinates that comprise the left-side of the range"""
411 return [(row, self.min_col) for row in range(self.min_row, self.max_row+1)]
414 @property
415 def right(self):
416 """A list of cell coordinates that comprise the right-side of the range"""
417 return [(row, self.max_col) for row in range(self.min_row, self.max_row+1)]
420class MultiCellRange(Strict):
423 ranges = Sequence(expected_type=CellRange)
426 def __init__(self, ranges=()):
427 if isinstance(ranges, str):
428 ranges = [CellRange(r) for r in ranges.split()]
429 self.ranges = ranges
432 def __contains__(self, coord):
433 if isinstance(coord, str):
434 coord = CellRange(coord)
435 for r in self.ranges:
436 if coord <= r:
437 return True
438 return False
441 def __repr__(self):
442 ranges = " ".join([str(r) for r in self.ranges])
443 return "<{0} [{1}]>".format(self.__class__.__name__, ranges)
446 def __str__(self):
447 ranges = u" ".join([str(r) for r in self.ranges])
448 return ranges
450 __str__ = __str__
453 def add(self, coord):
454 """
455 Add a cell coordinate or CellRange
456 """
457 cr = coord
458 if isinstance(coord, str):
459 cr = CellRange(coord)
460 elif not isinstance(coord, CellRange):
461 raise ValueError("You can only add CellRanges")
462 if cr not in self:
463 self.ranges.append(cr)
466 def __iadd__(self, coord):
467 self.add(coord)
468 return self
471 def __eq__(self, other):
472 if isinstance(other, str):
473 other = self.__class__(other)
474 return self.ranges == other.ranges
477 def __ne__(self, other):
478 return not self == other
481 def __bool__(self):
482 return bool(self.ranges)
485 def remove(self, coord):
486 if not isinstance(coord, CellRange):
487 coord = CellRange(coord)
488 self.ranges.remove(coord)
491 def __iter__(self):
492 for cr in self.ranges:
493 yield cr
496 def __copy__(self):
497 n = MultiCellRange()
499 for r in self.ranges:
500 n.ranges.append(copy(r))
501 return n