Updated script that can be controled by Nodejs web app
This commit is contained in:
17
lib/python3.13/site-packages/openpyxl/utils/__init__.py
Normal file
17
lib/python3.13/site-packages/openpyxl/utils/__init__.py
Normal file
@ -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
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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
|
240
lib/python3.13/site-packages/openpyxl/utils/cell.py
Normal file
240
lib/python3.13/site-packages/openpyxl/utils/cell.py
Normal file
@ -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
|
87
lib/python3.13/site-packages/openpyxl/utils/dataframe.py
Normal file
87
lib/python3.13/site-packages/openpyxl/utils/dataframe.py
Normal file
@ -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
|
140
lib/python3.13/site-packages/openpyxl/utils/datetime.py
Normal file
140
lib/python3.13/site-packages/openpyxl/utils/datetime.py
Normal file
@ -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)
|
43
lib/python3.13/site-packages/openpyxl/utils/escape.py
Normal file
43
lib/python3.13/site-packages/openpyxl/utils/escape.py
Normal file
@ -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
|
34
lib/python3.13/site-packages/openpyxl/utils/exceptions.py
Normal file
34
lib/python3.13/site-packages/openpyxl/utils/exceptions.py
Normal file
@ -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"""
|
24
lib/python3.13/site-packages/openpyxl/utils/formulas.py
Normal file
24
lib/python3.13/site-packages/openpyxl/utils/formulas.py
Normal file
@ -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")
|
49
lib/python3.13/site-packages/openpyxl/utils/indexed_list.py
Normal file
49
lib/python3.13/site-packages/openpyxl/utils/indexed_list.py
Normal file
@ -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]
|
60
lib/python3.13/site-packages/openpyxl/utils/inference.py
Normal file
60
lib/python3.13/site-packages/openpyxl/utils/inference.py
Normal file
@ -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()
|
22
lib/python3.13/site-packages/openpyxl/utils/protection.py
Normal file
22
lib/python3.13/site-packages/openpyxl/utils/protection.py
Normal file
@ -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:]
|
108
lib/python3.13/site-packages/openpyxl/utils/units.py
Normal file
108
lib/python3.13/site-packages/openpyxl/utils/units.py
Normal file
@ -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
|
Reference in New Issue
Block a user