aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/openpyxl/utils/cell.py
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/openpyxl/utils/cell.py')
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/utils/cell.py240
1 files changed, 240 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/utils/cell.py b/.venv/lib/python3.12/site-packages/openpyxl/utils/cell.py
new file mode 100644
index 00000000..f1ccc7d2
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/utils/cell.py
@@ -0,0 +1,240 @@
+# Copyright (c) 2010-2024 openpyxl
+
+"""
+Collection of utilities used within the package and also available for client code
+"""
+from functools import lru_cache
+from itertools import chain, product
+from string import ascii_uppercase, digits
+import re
+
+from .exceptions import CellCoordinatesException
+
+# constants
+COORD_RE = re.compile(r'^[$]?([A-Za-z]{1,3})[$]?(\d+)$')
+COL_RANGE = """[A-Z]{1,3}:[A-Z]{1,3}:"""
+ROW_RANGE = r"""\d+:\d+:"""
+RANGE_EXPR = r"""
+[$]?(?P<min_col>[A-Za-z]{1,3})?
+[$]?(?P<min_row>\d+)?
+(:[$]?(?P<max_col>[A-Za-z]{1,3})?
+[$]?(?P<max_row>\d+)?)?
+"""
+ABSOLUTE_RE = re.compile('^' + RANGE_EXPR +'$', re.VERBOSE)
+SHEET_TITLE = r"""
+(('(?P<quoted>([^']|'')*)')|(?P<notquoted>[^'^ ^!]*))!"""
+SHEETRANGE_RE = re.compile("""{0}(?P<cells>{1})(?=,?)""".format(
+ SHEET_TITLE, RANGE_EXPR), re.VERBOSE)
+
+
+def get_column_interval(start, end):
+ """
+ Given the start and end columns, return all the columns in the series.
+
+ The start and end columns can be either column letters or 1-based
+ indexes.
+ """
+ if isinstance(start, str):
+ start = column_index_from_string(start)
+ if isinstance(end, str):
+ end = column_index_from_string(end)
+ return [get_column_letter(x) for x in range(start, end + 1)]
+
+
+def coordinate_from_string(coord_string):
+ """Convert a coordinate string like 'B12' to a tuple ('B', 12)"""
+ match = COORD_RE.match(coord_string)
+ if not match:
+ msg = f"Invalid cell coordinates ({coord_string})"
+ raise CellCoordinatesException(msg)
+ column, row = match.groups()
+ row = int(row)
+ if not row:
+ msg = f"There is no row 0 ({coord_string})"
+ raise CellCoordinatesException(msg)
+ return column, row
+
+
+def absolute_coordinate(coord_string):
+ """Convert a coordinate to an absolute coordinate string (B12 -> $B$12)"""
+ m = ABSOLUTE_RE.match(coord_string)
+ if not m:
+ raise ValueError(f"{coord_string} is not a valid coordinate range")
+
+ d = m.groupdict('')
+ for k, v in d.items():
+ if v:
+ d[k] = f"${v}"
+
+ if d['max_col'] or d['max_row']:
+ fmt = "{min_col}{min_row}:{max_col}{max_row}"
+ else:
+ fmt = "{min_col}{min_row}"
+ return fmt.format(**d)
+
+
+__decimal_to_alpha = [""] + list(ascii_uppercase)
+
+@lru_cache(maxsize=None)
+def get_column_letter(col_idx):
+ """
+ Convert decimal column position to its ASCII (base 26) form.
+
+ Because column indices are 1-based, strides are actually pow(26, n) + 26
+ Hence, a correction is applied between pow(26, n) and pow(26, 2) + 26 to
+ prevent and additional column letter being prepended
+
+ "A" == 1 == pow(26, 0)
+ "Z" == 26 == pow(26, 0) + 26 // decimal equivalent 10
+ "AA" == 27 == pow(26, 1) + 1
+ "ZZ" == 702 == pow(26, 2) + 26 // decimal equivalent 100
+ """
+
+ if not 1 <= col_idx <= 18278:
+ raise ValueError("Invalid column index {0}".format(col_idx))
+
+ result = []
+
+ if col_idx < 26:
+ return __decimal_to_alpha[col_idx]
+
+ while col_idx:
+ col_idx, remainder = divmod(col_idx, 26)
+ result.insert(0, __decimal_to_alpha[remainder])
+ if not remainder:
+ col_idx -= 1
+ result.insert(0, "Z")
+
+ return "".join(result)
+
+
+__alpha_to_decimal = {letter:pos for pos, letter in enumerate(ascii_uppercase, 1)}
+__powers = (1, 26, 676)
+
+@lru_cache(maxsize=None)
+def column_index_from_string(col):
+ """
+ Convert ASCII column name (base 26) to decimal with 1-based index
+
+ Characters represent descending multiples of powers of 26
+
+ "AFZ" == 26 * pow(26, 0) + 6 * pow(26, 1) + 1 * pow(26, 2)
+ """
+ error_msg = f"'{col}' is not a valid column name. Column names are from A to ZZZ"
+ if len(col) > 3:
+ raise ValueError(error_msg)
+ idx = 0
+ col = reversed(col.upper())
+ for letter, power in zip(col, __powers):
+ try:
+ pos = __alpha_to_decimal[letter]
+ except KeyError:
+ raise ValueError(error_msg)
+ idx += pos * power
+ if not 0 < idx < 18279:
+ raise ValueError(error_msg)
+ return idx
+
+
+def range_boundaries(range_string):
+ """
+ Convert a range string into a tuple of boundaries:
+ (min_col, min_row, max_col, max_row)
+ Cell coordinates will be converted into a range with the cell at both end
+ """
+ msg = "{0} is not a valid coordinate or range".format(range_string)
+ m = ABSOLUTE_RE.match(range_string)
+ if not m:
+ raise ValueError(msg)
+
+ min_col, min_row, sep, max_col, max_row = m.groups()
+
+ if sep:
+ cols = min_col, max_col
+ rows = min_row, max_row
+
+ if not (
+ all(cols + rows) or
+ all(cols) and not any(rows) or
+ all(rows) and not any(cols)
+ ):
+ raise ValueError(msg)
+
+ if min_col is not None:
+ min_col = column_index_from_string(min_col)
+
+ if min_row is not None:
+ min_row = int(min_row)
+
+ if max_col is not None:
+ max_col = column_index_from_string(max_col)
+ else:
+ max_col = min_col
+
+ if max_row is not None:
+ max_row = int(max_row)
+ else:
+ max_row = min_row
+
+ return min_col, min_row, max_col, max_row
+
+
+def rows_from_range(range_string):
+ """
+ Get individual addresses for every cell in a range.
+ Yields one row at a time.
+ """
+ min_col, min_row, max_col, max_row = range_boundaries(range_string)
+ rows = range(min_row, max_row + 1)
+ cols = [get_column_letter(col) for col in range(min_col, max_col + 1)]
+ for row in rows:
+ yield tuple('{0}{1}'.format(col, row) for col in cols)
+
+
+def cols_from_range(range_string):
+ """
+ Get individual addresses for every cell in a range.
+ Yields one row at a time.
+ """
+ min_col, min_row, max_col, max_row = range_boundaries(range_string)
+ rows = range(min_row, max_row+1)
+ cols = (get_column_letter(col) for col in range(min_col, max_col+1))
+ for col in cols:
+ yield tuple('{0}{1}'.format(col, row) for row in rows)
+
+
+def coordinate_to_tuple(coordinate):
+ """
+ Convert an Excel style coordinate to (row, column) tuple
+ """
+ for idx, c in enumerate(coordinate):
+ if c in digits:
+ break
+ col = coordinate[:idx]
+ row = coordinate[idx:]
+ return int(row), column_index_from_string(col)
+
+
+def range_to_tuple(range_string):
+ """
+ Convert a worksheet range to the sheetname and maximum and minimum
+ coordinate indices
+ """
+ m = SHEETRANGE_RE.match(range_string)
+ if m is None:
+ raise ValueError("Value must be of the form sheetname!A1:E4")
+ sheetname = m.group("quoted") or m.group("notquoted")
+ cells = m.group("cells")
+ boundaries = range_boundaries(cells)
+ return sheetname, boundaries
+
+
+def quote_sheetname(sheetname):
+ """
+ Add quotes around sheetnames if they contain spaces.
+ """
+ if "'" in sheetname:
+ sheetname = sheetname.replace("'", "''")
+
+ sheetname = u"'{0}'".format(sheetname)
+ return sheetname