diff options
Diffstat (limited to '.venv/lib/python3.12/site-packages/openpyxl/utils')
12 files changed, 850 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/utils/__init__.py b/.venv/lib/python3.12/site-packages/openpyxl/utils/__init__.py new file mode 100644 index 00000000..f6132636 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/utils/__init__.py @@ -0,0 +1,17 @@ +# Copyright (c) 2010-2024 openpyxl + + +from .cell import ( + absolute_coordinate, + cols_from_range, + column_index_from_string, + coordinate_to_tuple, + get_column_letter, + get_column_interval, + quote_sheetname, + range_boundaries, + range_to_tuple, + rows_from_range, +) + +from .formulas import FORMULAE diff --git a/.venv/lib/python3.12/site-packages/openpyxl/utils/bound_dictionary.py b/.venv/lib/python3.12/site-packages/openpyxl/utils/bound_dictionary.py new file mode 100644 index 00000000..20cbd1c4 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/utils/bound_dictionary.py @@ -0,0 +1,26 @@ +# Copyright (c) 2010-2024 openpyxl + +from collections import defaultdict + + +class BoundDictionary(defaultdict): + """ + A default dictionary where elements are tightly coupled. + + The factory method is responsible for binding the parent object to the child. + + If a reference attribute is assigned then child objects will have the key assigned to this. + + Otherwise it's just a defaultdict. + """ + + def __init__(self, reference=None, *args, **kw): + self.reference = reference + super().__init__(*args, **kw) + + + def __getitem__(self, key): + value = super().__getitem__(key) + if self.reference is not None: + setattr(value, self.reference, key) + return value 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 diff --git a/.venv/lib/python3.12/site-packages/openpyxl/utils/dataframe.py b/.venv/lib/python3.12/site-packages/openpyxl/utils/dataframe.py new file mode 100644 index 00000000..f56a4887 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/utils/dataframe.py @@ -0,0 +1,87 @@ +# Copyright (c) 2010-2024 openpyxl + +from itertools import accumulate +import operator +import numpy +from openpyxl.compat.product import prod + + +def dataframe_to_rows(df, index=True, header=True): + """ + Convert a Pandas dataframe into something suitable for passing into a worksheet. + If index is True then the index will be included, starting one row below the header. + If header is True then column headers will be included starting one column to the right. + Formatting should be done by client code. + """ + from pandas import Timestamp + + if header: + if df.columns.nlevels > 1: + rows = expand_index(df.columns, header) + else: + rows = [list(df.columns.values)] + for row in rows: + n = [] + for v in row: + if isinstance(v, numpy.datetime64): + v = Timestamp(v) + n.append(v) + row = n + if index: + row = [None]*df.index.nlevels + row + yield row + + if index: + yield df.index.names + + expanded = ([v] for v in df.index) + if df.index.nlevels > 1: + expanded = expand_index(df.index) + + # Using the expanded index is preferable to df.itertuples(index=True) so that we have 'None' inserted where applicable + for (df_index, row) in zip(expanded, df.itertuples(index=False)): + row = list(row) + if index: + row = df_index + row + yield row + + +def expand_index(index, header=False): + """ + Expand axis or column Multiindex + For columns use header = True + For axes use header = False (default) + """ + + # For each element of the index, zip the members with the previous row + # If the 2 elements of the zipped list do not match, we can insert the new value into the row + # or if an earlier member was different, all later members should be added to the row + values = list(index.values) + previous_value = [None] * len(values[0]) + result = [] + + for value in values: + row = [None] * len(value) + + # Once there's a difference in member of an index with the prior index, we need to store all subsequent members in the row + prior_change = False + for idx, (current_index_member, previous_index_member) in enumerate(zip(value, previous_value)): + + if current_index_member != previous_index_member or prior_change: + row[idx] = current_index_member + prior_change = True + + previous_value = value + + # If this is for a row index, we're already returning a row so just yield + if not header: + yield row + else: + result.append(row) + + # If it's for a header, we need to transpose to get it in row order + # Example: result = [['A', 'A'], [None, 'B']] -> [['A', None], ['A', 'B']] + if header: + result = numpy.array(result).transpose().tolist() + for row in result: + yield row diff --git a/.venv/lib/python3.12/site-packages/openpyxl/utils/datetime.py b/.venv/lib/python3.12/site-packages/openpyxl/utils/datetime.py new file mode 100644 index 00000000..bf7e5006 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/utils/datetime.py @@ -0,0 +1,140 @@ +# Copyright (c) 2010-2024 openpyxl + +"""Manage Excel date weirdness.""" + +# Python stdlib imports +import datetime +from math import isnan +import re + + +# constants +MAC_EPOCH = datetime.datetime(1904, 1, 1) +WINDOWS_EPOCH = datetime.datetime(1899, 12, 30) +CALENDAR_WINDOWS_1900 = 2415018.5 # Julian date of WINDOWS_EPOCH +CALENDAR_MAC_1904 = 2416480.5 # Julian date of MAC_EPOCH +CALENDAR_WINDOWS_1900 = WINDOWS_EPOCH +CALENDAR_MAC_1904 = MAC_EPOCH +SECS_PER_DAY = 86400 + +ISO_FORMAT = '%Y-%m-%dT%H:%M:%SZ' +ISO_REGEX = re.compile(r''' +(?P<date>(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2}))?T? +(?P<time>(?P<hour>\d{2}):(?P<minute>\d{2})(:(?P<second>\d{2})(?P<microsecond>\.\d{1,3})?)?)?Z?''', + re.VERBOSE) +ISO_DURATION = re.compile(r'PT((?P<hours>\d+)H)?((?P<minutes>\d+)M)?((?P<seconds>\d+(\.\d{1,3})?)S)?') + + +def to_ISO8601(dt): + """Convert from a datetime to a timestamp string.""" + if hasattr(dt, "microsecond") and dt.microsecond: + return dt.isoformat(timespec="milliseconds") + return dt.isoformat() + + +def from_ISO8601(formatted_string): + """Convert from a timestamp string to a datetime object. According to + 18.17.4 in the specification the following ISO 8601 formats are + supported. + + Dates B.1.1 and B.2.1 + Times B.1.2 and B.2.2 + Datetimes B.1.3 and B.2.3 + + There is no concept of timedeltas in the specification, but Excel + writes them (in strict OOXML mode), so these are also understood. + """ + if not formatted_string: + return None + + match = ISO_REGEX.match(formatted_string) + if match and any(match.groups()): + parts = match.groupdict(0) + for key in ["year", "month", "day", "hour", "minute", "second"]: + if parts[key]: + parts[key] = int(parts[key]) + + if parts["microsecond"]: + parts["microsecond"] = int(float(parts['microsecond']) * 1_000_000) + + if not parts["date"]: + dt = datetime.time(parts['hour'], parts['minute'], parts['second'], parts["microsecond"]) + elif not parts["time"]: + dt = datetime.date(parts['year'], parts['month'], parts['day']) + else: + del parts["time"] + del parts["date"] + dt = datetime.datetime(**parts) + return dt + + match = ISO_DURATION.match(formatted_string) + if match and any(match.groups()): + parts = match.groupdict(0) + for key, val in parts.items(): + if val: + parts[key] = float(val) + return datetime.timedelta(**parts) + + raise ValueError("Invalid datetime value {}".format(formatted_string)) + + +def to_excel(dt, epoch=WINDOWS_EPOCH): + """Convert Python datetime to Excel serial""" + if isinstance(dt, datetime.time): + return time_to_days(dt) + if isinstance(dt, datetime.timedelta): + return timedelta_to_days(dt) + if isnan(dt.year): # Pandas supports Not a Date + return + + if not hasattr(dt, "date"): + dt = datetime.datetime.combine(dt, datetime.time()) + + # rebase on epoch and adjust for < 1900-03-01 + days = (dt - epoch).days + if 0 < days <= 60 and epoch == WINDOWS_EPOCH: + days -= 1 + return days + time_to_days(dt) + + +def from_excel(value, epoch=WINDOWS_EPOCH, timedelta=False): + """Convert Excel serial to Python datetime""" + if value is None: + return + + if timedelta: + td = datetime.timedelta(days=value) + if td.microseconds: + # round to millisecond precision + td = datetime.timedelta(seconds=td.total_seconds() // 1, + microseconds=round(td.microseconds, -3)) + return td + + day, fraction = divmod(value, 1) + diff = datetime.timedelta(milliseconds=round(fraction * SECS_PER_DAY * 1000)) + if 0 <= value < 1 and diff.days == 0: + return days_to_time(diff) + if 0 < value < 60 and epoch == WINDOWS_EPOCH: + day += 1 + return epoch + datetime.timedelta(days=day) + diff + + +def time_to_days(value): + """Convert a time value to fractions of day""" + return ( + (value.hour * 3600) + + (value.minute * 60) + + value.second + + value.microsecond / 10**6 + ) / SECS_PER_DAY + + +def timedelta_to_days(value): + """Convert a timedelta value to fractions of a day""" + return value.total_seconds() / SECS_PER_DAY + + +def days_to_time(value): + mins, seconds = divmod(value.seconds, 60) + hours, mins = divmod(mins, 60) + return datetime.time(hours, mins, seconds, value.microseconds) diff --git a/.venv/lib/python3.12/site-packages/openpyxl/utils/escape.py b/.venv/lib/python3.12/site-packages/openpyxl/utils/escape.py new file mode 100644 index 00000000..a8985343 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/utils/escape.py @@ -0,0 +1,43 @@ +# Copyright (c) 2010-2024 openpyxl + +""" +OOXML has non-standard escaping for characters < \031 +""" + +import re + + +def escape(value): + r""" + Convert ASCII < 31 to OOXML: \n == _x + hex(ord(\n)) + _ + """ + + CHAR_REGEX = re.compile(r"[\001-\031]") + + def _sub(match): + """ + Callback to escape chars + """ + return "_x{:0>4x}_".format(ord(match.group(0))) + + return CHAR_REGEX.sub(_sub, value) + + +def unescape(value): + r""" + Convert escaped strings to ASCIII: _x000a_ == \n + """ + + + ESCAPED_REGEX = re.compile("_x([0-9A-Fa-f]{4})_") + + def _sub(match): + """ + Callback to unescape chars + """ + return chr(int(match.group(1), 16)) + + if "_x" in value: + value = ESCAPED_REGEX.sub(_sub, value) + + return value diff --git a/.venv/lib/python3.12/site-packages/openpyxl/utils/exceptions.py b/.venv/lib/python3.12/site-packages/openpyxl/utils/exceptions.py new file mode 100644 index 00000000..7b05f742 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/utils/exceptions.py @@ -0,0 +1,34 @@ +# Copyright (c) 2010-2024 openpyxl + + +"""Definitions for openpyxl shared exception classes.""" + + +class CellCoordinatesException(Exception): + """Error for converting between numeric and A1-style cell references.""" + + +class IllegalCharacterError(Exception): + """The data submitted which cannot be used directly in Excel files. It + must be removed or escaped.""" + + +class NamedRangeException(Exception): + """Error for badly formatted named ranges.""" + + +class SheetTitleException(Exception): + """Error for bad sheet names.""" + + +class InvalidFileException(Exception): + """Error for trying to open a non-ooxml file.""" + + +class ReadOnlyWorkbookException(Exception): + """Error for trying to modify a read-only workbook""" + + +class WorkbookAlreadySaved(Exception): + """Error when attempting to perform operations on a dump workbook + while it has already been dumped once""" diff --git a/.venv/lib/python3.12/site-packages/openpyxl/utils/formulas.py b/.venv/lib/python3.12/site-packages/openpyxl/utils/formulas.py new file mode 100644 index 00000000..aab9961b --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/utils/formulas.py @@ -0,0 +1,24 @@ +# Copyright (c) 2010-2024 openpyxl + +""" +List of builtin formulae +""" + +FORMULAE = ("CUBEKPIMEMBER", "CUBEMEMBER", "CUBEMEMBERPROPERTY", "CUBERANKEDMEMBER", "CUBESET", "CUBESETCOUNT", "CUBEVALUE", "DAVERAGE", "DCOUNT", "DCOUNTA", "DGET", "DMAX", "DMIN", "DPRODUCT", "DSTDEV", "DSTDEVP", "DSUM", "DVAR", "DVARP", "DATE", "DATEDIF", "DATEVALUE", "DAY", "DAYS360", "EDATE", "EOMONTH", "HOUR", "MINUTE", "MONTH", "NETWORKDAYS", "NETWORKDAYS.INTL", "NOW", "SECOND", "TIME", "TIMEVALUE", "TODAY", "WEEKDAY", "WEEKNUM", "WORKDAY", "WORKDAY.INTL", "YEAR", "YEARFRAC", "BESSELI", "BESSELJ", "BESSELK", "BESSELY", "BIN2DEC", "BIN2HEX", "BIN2OCT", "COMPLEX", "CONVERT", "DEC2BIN", "DEC2HEX", "DEC2OCT", "DELTA", "ERF", "ERFC", "GESTEP", "HEX2BIN", "HEX2DEC", "HEX2OCT", "IMABS", "IMAGINARY", "IMARGUMENT", "IMCONJUGATE", "IMCOS", "IMDIV", "IMEXP", "IMLN", "IMLOG10", "IMLOG2", "IMPOWER", "IMPRODUCT", "IMREAL", "IMSIN", "IMSQRT", "IMSUB", "IMSUM", "OCT2BIN", "OCT2DEC", "OCT2HEX", "ACCRINT", "ACCRINTM", "AMORDEGRC", "AMORLINC", "COUPDAYBS", "COUPDAYS", "COUPDAYSNC", "COUPNCD", "COUPNUM", "COUPPCD", "CUMIPMT", "CUMPRINC", "DB", "DDB", "DISC", "DOLLARDE", "DOLLARFR", "DURATION", "EFFECT", "FV", "FVSCHEDULE", "INTRATE", "IPMT", "IRR", "ISPMT", "MDURATION", "MIRR", "NOMINAL", "NPER", "NPV", "ODDFPRICE", "ODDFYIELD", "ODDLPRICE", "ODDLYIELD", "PMT", "PPMT", "PRICE", "PRICEDISC", "PRICEMAT", "PV", "RATE", "RECEIVED", "SLN", "SYD", "TBILLEQ", "TBILLPRICE", "TBILLYIELD", "VDB", "XIRR", "XNPV", "YIELD", "YIELDDISC", "YIELDMAT", "CELL", "ERROR.TYPE", "INFO", "ISBLANK", "ISERR", "ISERROR", "ISEVEN", "ISLOGICAL", "ISNA", "ISNONTEXT", "ISNUMBER", "ISODD", "ISREF", "ISTEXT", "N", "NA", "TYPE", "AND", "FALSE", "IF", "IFERROR", "NOT", "OR", "TRUE", "ADDRESS", "AREAS", "CHOOSE", "COLUMN", "COLUMNS", "GETPIVOTDATA", "HLOOKUP", "HYPERLINK", "INDEX", "INDIRECT", "LOOKUP", "MATCH", "OFFSET", "ROW", "ROWS", "RTD", "TRANSPOSE", "VLOOKUP", "ABS", "ACOS", "ACOSH", "ASIN", "ASINH", "ATAN", "ATAN2", "ATANH", "CEILING", "COMBIN", "COS", "COSH", "DEGREES", "ECMA.CEILING", "EVEN", "EXP", "FACT", "FACTDOUBLE", "FLOOR", "GCD", "INT", "ISO.CEILING", "LCM", "LN", "LOG", "LOG10", "MDETERM", "MINVERSE", "MMULT", "MOD", "MROUND", "MULTINOMIAL", "ODD", "PI", "POWER", "PRODUCT", "QUOTIENT", "RADIANS", "RAND", "RANDBETWEEN", "ROMAN", "ROUND", "ROUNDDOWN", "ROUNDUP", "SERIESSUM", "SIGN", "SIN", "SINH", "SQRT", "SQRTPI", "SUBTOTAL", "SUM", "SUMIF", "SUMIFS", "SUMPRODUCT", "SUMSQ", "SUMX2MY2", "SUMX2PY2", "SUMXMY2", "TAN", "TANH", "TRUNC", "AVEDEV", "AVERAGE", "AVERAGEA", "AVERAGEIF", "AVERAGEIFS", "BETADIST", "BETAINV", "BINOMDIST", "CHIDIST", "CHIINV", "CHITEST", "CONFIDENCE", "CORREL", "COUNT", "COUNTA", "COUNTBLANK", "COUNTIF", "COUNTIFS", "COVAR", "CRITBINOM", "DEVSQ", "EXPONDIST", "FDIST", "FINV", "FISHER", "FISHERINV", "FORECAST", "FREQUENCY", "FTEST", "GAMMADIST", "GAMMAINV", "GAMMALN", "GEOMEAN", "GROWTH", "HARMEAN", "HYPGEOMDIST", "INTERCEPT", "KURT", "LARGE", "LINEST", "LOGEST", "LOGINV", "LOGNORMDIST", "MAX", "MAXA", "MEDIAN", "MIN", "MINA", "MODE", "NEGBINOMDIST", "NORMDIST", "NORMINV", "NORMSDIST", "NORMSINV", "PEARSON", "PERCENTILE", "PERCENTRANK", "PERMUT", "POISSON", "PROB", "QUARTILE", "RANK", "RSQ", "SKEW", "SLOPE", "SMALL", "STANDARDIZE", "STDEV", "STDEVA", "STDEVP", "STDEVPA", "STEYX", "TDIST", "TINV", "TREND", "TRIMMEAN", "TTEST", "VAR", "VARA", "VARP", "VARPA", "WEIBULL", "ZTEST", "ASC", "BAHTTEXT", "CHAR", "CLEAN", "CODE", "CONCATENATE", "DOLLAR", "EXACT", "FIND", "FINDB", "FIXED", "JIS", "LEFT", "LEFTB", "LEN", "LENB", "LOWER", "MID", "MIDB", "PHONETIC", "PROPER", "REPLACE", "REPLACEB", "REPT", "RIGHT", "RIGHTB", "SEARCH", "SEARCHB", "SUBSTITUTE", "T", "TEXT", "TRIM", "UPPER", "VALUE") + +FORMULAE = frozenset(FORMULAE) + + +from openpyxl.formula import Tokenizer + + +def validate(formula): + """ + Utility function for checking whether a formula is syntactically correct + """ + assert formula.startswith("=") + formula = Tokenizer(formula) + for t in formula.items: + if t.type == "FUNC" and t.subtype == "OPEN": + if not t.value.startswith("_xlfn.") and t.value[:-1] not in FORMULAE: + raise ValueError(f"Unknown function {t.value} in {formula.formula}. The function may need a prefix") diff --git a/.venv/lib/python3.12/site-packages/openpyxl/utils/indexed_list.py b/.venv/lib/python3.12/site-packages/openpyxl/utils/indexed_list.py new file mode 100644 index 00000000..753acf09 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/utils/indexed_list.py @@ -0,0 +1,49 @@ +# Copyright (c) 2010-2024 openpyxl + + +class IndexedList(list): + """ + List with optimised access by value + Based on Alex Martelli's recipe + + http://code.activestate.com/recipes/52303-the-auxiliary-dictionary-idiom-for-sequences-with-/ + """ + + _dict = {} + + def __init__(self, iterable=None): + self.clean = True + self._dict = {} + if iterable is not None: + self.clean = False + for idx, val in enumerate(iterable): + self._dict[val] = idx + list.append(self, val) + + def _rebuild_dict(self): + self._dict = {} + idx = 0 + for value in self: + if value not in self._dict: + self._dict[value] = idx + idx += 1 + self.clean = True + + def __contains__(self, value): + if not self.clean: + self._rebuild_dict() + return value in self._dict + + def index(self, value): + if value in self: + return self._dict[value] + raise ValueError + + def append(self, value): + if value not in self._dict: + self._dict[value] = len(self) + list.append(self, value) + + def add(self, value): + self.append(value) + return self._dict[value] diff --git a/.venv/lib/python3.12/site-packages/openpyxl/utils/inference.py b/.venv/lib/python3.12/site-packages/openpyxl/utils/inference.py new file mode 100644 index 00000000..aff02a2b --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/utils/inference.py @@ -0,0 +1,60 @@ +# Copyright (c) 2010-2024 openpyxl + +""" +Type inference functions +""" +import datetime +import re + +from openpyxl.styles import numbers + +PERCENT_REGEX = re.compile(r'^(?P<number>\-?[0-9]*\.?[0-9]*\s?)\%$') +TIME_REGEX = re.compile(r""" +^(?: # HH:MM and HH:MM:SS +(?P<hour>[0-1]{0,1}[0-9]{2}): +(?P<minute>[0-5][0-9]):? +(?P<second>[0-5][0-9])?$) +| +^(?: # MM:SS. +([0-5][0-9]): +([0-5][0-9])?\. +(?P<microsecond>\d{1,6})) +""", re.VERBOSE) +NUMBER_REGEX = re.compile(r'^-?([\d]|[\d]+\.[\d]*|\.[\d]+|[1-9][\d]+\.?[\d]*)((E|e)[-+]?[\d]+)?$') + + +def cast_numeric(value): + """Explicitly convert a string to a numeric value""" + if NUMBER_REGEX.match(value): + try: + return int(value) + except ValueError: + return float(value) + + +def cast_percentage(value): + """Explicitly convert a string to numeric value and format as a + percentage""" + match = PERCENT_REGEX.match(value) + if match: + return float(match.group('number')) / 100 + + + +def cast_time(value): + """Explicitly convert a string to a number and format as datetime or + time""" + match = TIME_REGEX.match(value) + if match: + if match.group("microsecond") is not None: + value = value[:12] + pattern = "%M:%S.%f" + #fmt = numbers.FORMAT_DATE_TIME5 + elif match.group('second') is None: + #fmt = numbers.FORMAT_DATE_TIME3 + pattern = "%H:%M" + else: + pattern = "%H:%M:%S" + #fmt = numbers.FORMAT_DATE_TIME6 + value = datetime.datetime.strptime(value, pattern) + return value.time() diff --git a/.venv/lib/python3.12/site-packages/openpyxl/utils/protection.py b/.venv/lib/python3.12/site-packages/openpyxl/utils/protection.py new file mode 100644 index 00000000..cc7707ee --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/utils/protection.py @@ -0,0 +1,22 @@ +# Copyright (c) 2010-2024 openpyxl + + +def hash_password(plaintext_password=''): + """ + Create a password hash from a given string for protecting a worksheet + only. This will not work for encrypting a workbook. + + This method is based on the algorithm provided by + Daniel Rentz of OpenOffice and the PEAR package + Spreadsheet_Excel_Writer by Xavier Noguer <xnoguer@rezebra.com>. + See also http://blogs.msdn.com/b/ericwhite/archive/2008/02/23/the-legacy-hashing-algorithm-in-open-xml.aspx + """ + password = 0x0000 + for idx, char in enumerate(plaintext_password, 1): + value = ord(char) << idx + rotated_bits = value >> 15 + value &= 0x7fff + password ^= (value | rotated_bits) + password ^= len(plaintext_password) + password ^= 0xCE4B + return str(hex(password)).upper()[2:] diff --git a/.venv/lib/python3.12/site-packages/openpyxl/utils/units.py b/.venv/lib/python3.12/site-packages/openpyxl/utils/units.py new file mode 100644 index 00000000..19f23c5b --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/utils/units.py @@ -0,0 +1,108 @@ + +# Copyright (c) 2010-2024 openpyxl + +import math + + +#constants + +DEFAULT_ROW_HEIGHT = 15. # Default row height measured in point size. +BASE_COL_WIDTH = 8 # in characters +DEFAULT_COLUMN_WIDTH = BASE_COL_WIDTH + 5 +# = baseColumnWidth + {margin padding (2 pixels on each side, totalling 4 pixels)} + {gridline (1pixel)} + + +DEFAULT_LEFT_MARGIN = 0.7 # in inches, = right margin +DEFAULT_TOP_MARGIN = 0.7874 # in inches = bottom margin +DEFAULT_HEADER = 0.3 # in inches + + +# Conversion functions +""" +From the ECMA Spec (4th Edition part 1) +Page setup: "Left Page Margin in inches" p. 1647 + +Docs from +http://startbigthinksmall.wordpress.com/2010/01/04/points-inches-and-emus-measuring-units-in-office-open-xml/ + +See also http://msdn.microsoft.com/en-us/library/dd560821(v=office.12).aspx + +dxa: The main unit in OOXML is a twentieth of a point. Also called twips. +pt: point. In Excel there are 72 points to an inch +hp: half-points are used to specify font sizes. A font-size of 12pt equals 24 half points +pct: Half-points are used to specify font sizes. A font-size of 12pt equals 24 half points + +EMU: English Metric Unit, EMUs are used for coordinates in vector-based +drawings and embedded pictures. One inch equates to 914400 EMUs and a +centimeter is 360000. For bitmaps the default resolution is 96 dpi (known as +PixelsPerInch in Excel). Spec p. 1122 + +For radial geometry Excel uses integer units of 1/60000th of a degree. +""" + + + +def inch_to_dxa(value): + """1 inch = 72 * 20 dxa""" + return int(value * 20 * 72) + +def dxa_to_inch(value): + return value / 72 / 20 + + +def dxa_to_cm(value): + return 2.54 * dxa_to_inch(value) + +def cm_to_dxa(value): + emu = cm_to_EMU(value) + inch = EMU_to_inch(emu) + return inch_to_dxa(inch) + + +def pixels_to_EMU(value): + """1 pixel = 9525 EMUs""" + return int(value * 9525) + +def EMU_to_pixels(value): + return round(value / 9525) + + +def cm_to_EMU(value): + """1 cm = 360000 EMUs""" + return int(value * 360000) + +def EMU_to_cm(value): + return round(value / 360000, 4) + + +def inch_to_EMU(value): + """1 inch = 914400 EMUs""" + return int(value * 914400) + +def EMU_to_inch(value): + return round(value / 914400, 4) + + +def pixels_to_points(value, dpi=96): + """96 dpi, 72i""" + return value * 72 / dpi + + +def points_to_pixels(value, dpi=96): + return int(math.ceil(value * dpi / 72)) + + +def degrees_to_angle(value): + """1 degree = 60000 angles""" + return int(round(value * 60000)) + + +def angle_to_degrees(value): + return round(value / 60000, 2) + + +def short_color(color): + """ format a color to its short size """ + if len(color) > 6: + return color[2:] + return color |