Updated script that can be controled by Nodejs web app

This commit is contained in:
mac OS
2024-11-25 12:24:18 +07:00
parent c440eda1f4
commit 8b0ab2bd3a
8662 changed files with 1803808 additions and 34 deletions

View 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

View File

@ -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

View 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

View 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

View 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)

View 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

View 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"""

View 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")

View 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]

View 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()

View 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:]

View 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