numbers-parser 4.14.3__py3-none-any.whl → 4.15.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- numbers_parser/__init__.py +1 -0
- numbers_parser/_cat_numbers.py +15 -6
- numbers_parser/cell.py +28 -202
- numbers_parser/constants.py +8 -4
- numbers_parser/document.py +26 -3
- numbers_parser/formula.py +423 -56
- numbers_parser/generated/TSCEArchives_pb2.py +209 -193
- numbers_parser/generated/TSSArchives_pb2.py +36 -36
- numbers_parser/generated/TSTArchives_pb2.py +328 -332
- numbers_parser/generated/mapping.py +1 -2
- numbers_parser/model.py +281 -187
- numbers_parser/numbers_uuid.py +6 -0
- numbers_parser/tokenizer.py +548 -0
- numbers_parser/xrefs.py +850 -0
- {numbers_parser-4.14.3.dist-info → numbers_parser-4.15.1.dist-info}/METADATA +8 -25
- {numbers_parser-4.14.3.dist-info → numbers_parser-4.15.1.dist-info}/RECORD +19 -17
- {numbers_parser-4.14.3.dist-info → numbers_parser-4.15.1.dist-info}/WHEEL +1 -1
- {numbers_parser-4.14.3.dist-info → numbers_parser-4.15.1.dist-info}/LICENSE.rst +0 -0
- {numbers_parser-4.14.3.dist-info → numbers_parser-4.15.1.dist-info}/entry_points.txt +0 -0
numbers_parser/__init__.py
CHANGED
|
@@ -13,6 +13,7 @@ with warnings.catch_warnings():
|
|
|
13
13
|
from numbers_parser.constants import * # noqa: F403
|
|
14
14
|
from numbers_parser.document import * # noqa: F403
|
|
15
15
|
from numbers_parser.exceptions import * # noqa: F403
|
|
16
|
+
from numbers_parser.xrefs import * # noqa: F403
|
|
16
17
|
|
|
17
18
|
__version__ = importlib.metadata.version("numbers-parser")
|
|
18
19
|
|
numbers_parser/_cat_numbers.py
CHANGED
|
@@ -5,7 +5,15 @@ import sys
|
|
|
5
5
|
|
|
6
6
|
from sigfig import round as sigfig
|
|
7
7
|
|
|
8
|
-
from numbers_parser import
|
|
8
|
+
from numbers_parser import (
|
|
9
|
+
Document,
|
|
10
|
+
ErrorCell,
|
|
11
|
+
FileError,
|
|
12
|
+
FileFormatError,
|
|
13
|
+
NumberCell,
|
|
14
|
+
UnsupportedError,
|
|
15
|
+
_get_version,
|
|
16
|
+
)
|
|
9
17
|
from numbers_parser import __name__ as numbers_parser_name
|
|
10
18
|
from numbers_parser.constants import MAX_SIGNIFICANT_DIGITS
|
|
11
19
|
from numbers_parser.experimental import _enable_experimental_features
|
|
@@ -137,11 +145,12 @@ def main() -> None:
|
|
|
137
145
|
print_table_names(filename)
|
|
138
146
|
else:
|
|
139
147
|
print_table(args, filename)
|
|
140
|
-
except FileFormatError as e: # noqa: PERF203
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
148
|
+
except (FileFormatError, FileError, UnsupportedError) as e: # noqa: PERF203
|
|
149
|
+
err_str = str(e)
|
|
150
|
+
if filename in err_str:
|
|
151
|
+
print(err_str, file=sys.stderr)
|
|
152
|
+
else:
|
|
153
|
+
print(f"{filename}: {err_str}", file=sys.stderr)
|
|
145
154
|
sys.exit(1)
|
|
146
155
|
|
|
147
156
|
|
numbers_parser/cell.py
CHANGED
|
@@ -22,6 +22,7 @@ from numbers_parser.constants import (
|
|
|
22
22
|
CURRENCY_CELL_TYPE,
|
|
23
23
|
CUSTOM_TEXT_PLACEHOLDER,
|
|
24
24
|
DATETIME_FIELD_MAP,
|
|
25
|
+
DECIMAL128_BIAS,
|
|
25
26
|
DECIMAL_PLACES_AUTO,
|
|
26
27
|
DEFAULT_ALIGNMENT,
|
|
27
28
|
DEFAULT_BORDER_COLOR,
|
|
@@ -55,6 +56,7 @@ from numbers_parser.constants import (
|
|
|
55
56
|
)
|
|
56
57
|
from numbers_parser.currencies import CURRENCIES, CURRENCY_SYMBOLS
|
|
57
58
|
from numbers_parser.exceptions import UnsupportedError, UnsupportedWarning
|
|
59
|
+
from numbers_parser.formula import Formula
|
|
58
60
|
from numbers_parser.generated import TSPMessages_pb2 as TSPMessages
|
|
59
61
|
from numbers_parser.generated import TSTArchives_pb2 as TSTArchives
|
|
60
62
|
from numbers_parser.generated.TSWPArchives_pb2 import (
|
|
@@ -62,12 +64,14 @@ from numbers_parser.generated.TSWPArchives_pb2 import (
|
|
|
62
64
|
)
|
|
63
65
|
from numbers_parser.numbers_cache import Cacheable, cache
|
|
64
66
|
from numbers_parser.numbers_uuid import NumbersUUID
|
|
67
|
+
from numbers_parser.xrefs import xl_range
|
|
65
68
|
|
|
66
69
|
logger = logging.getLogger(numbers_parser_name)
|
|
67
70
|
debug = logger.debug
|
|
68
71
|
|
|
69
72
|
|
|
70
73
|
__all__ = [
|
|
74
|
+
"RGB",
|
|
71
75
|
"Alignment",
|
|
72
76
|
"BackgroundImage",
|
|
73
77
|
"BoolCell",
|
|
@@ -88,14 +92,9 @@ __all__ = [
|
|
|
88
92
|
"MergedCell",
|
|
89
93
|
"NumberCell",
|
|
90
94
|
"RichTextCell",
|
|
91
|
-
"RGB",
|
|
92
95
|
"Style",
|
|
93
96
|
"TextCell",
|
|
94
97
|
"VerticalJustification",
|
|
95
|
-
"xl_cell_to_rowcol",
|
|
96
|
-
"xl_col_to_name",
|
|
97
|
-
"xl_range",
|
|
98
|
-
"xl_rowcol_to_cell",
|
|
99
98
|
]
|
|
100
99
|
|
|
101
100
|
|
|
@@ -562,8 +561,6 @@ class CellStorageFlags:
|
|
|
562
561
|
_rich_id: int = None
|
|
563
562
|
_cell_style_id: int = None
|
|
564
563
|
_text_style_id: int = None
|
|
565
|
-
# _cond_style_id: int = None
|
|
566
|
-
# _cond_rule_style_id: int = None
|
|
567
564
|
_formula_id: int = None
|
|
568
565
|
_control_id: int = None
|
|
569
566
|
_formula_error_id: int = None
|
|
@@ -574,8 +571,6 @@ class CellStorageFlags:
|
|
|
574
571
|
_duration_format_id: int = None
|
|
575
572
|
_text_format_id: int = None
|
|
576
573
|
_bool_format_id: int = None
|
|
577
|
-
# _comment_id: int = None
|
|
578
|
-
# _import_warning_id: int = None
|
|
579
574
|
|
|
580
575
|
def __str__(self) -> str:
|
|
581
576
|
fields = [
|
|
@@ -599,6 +594,7 @@ class Cell(CellStorageFlags, Cacheable):
|
|
|
599
594
|
self.row = row
|
|
600
595
|
self.col = col
|
|
601
596
|
self._is_bulleted = False
|
|
597
|
+
self._formula_id = None
|
|
602
598
|
self._storage = None
|
|
603
599
|
self._style = None
|
|
604
600
|
self._d128 = None
|
|
@@ -641,11 +637,9 @@ class Cell(CellStorageFlags, Cacheable):
|
|
|
641
637
|
@property
|
|
642
638
|
def is_formula(self) -> bool:
|
|
643
639
|
"""bool: ``True`` if the cell contains a formula."""
|
|
644
|
-
|
|
645
|
-
return table_formulas.is_formula(self.row, self.col)
|
|
640
|
+
return self._formula_id is not None
|
|
646
641
|
|
|
647
642
|
@property
|
|
648
|
-
@cache(num_args=0)
|
|
649
643
|
def formula(self) -> str:
|
|
650
644
|
"""
|
|
651
645
|
str: The formula in a cell.
|
|
@@ -666,6 +660,17 @@ class Cell(CellStorageFlags, Cacheable):
|
|
|
666
660
|
return table_formulas.formula(self._formula_id, self.row, self.col)
|
|
667
661
|
return None
|
|
668
662
|
|
|
663
|
+
@formula.setter
|
|
664
|
+
def formula(self, value: str) -> None:
|
|
665
|
+
self._formula_id = Formula.from_str(
|
|
666
|
+
self._model,
|
|
667
|
+
self._table_id,
|
|
668
|
+
self.row,
|
|
669
|
+
self.col,
|
|
670
|
+
value,
|
|
671
|
+
)
|
|
672
|
+
self._model.add_formula_dependency(self.row, self.col, self._table_id)
|
|
673
|
+
|
|
669
674
|
@property
|
|
670
675
|
def is_bulleted(self) -> bool:
|
|
671
676
|
"""bool: ``True`` if the cell contains text bullets."""
|
|
@@ -740,7 +745,7 @@ class Cell(CellStorageFlags, Cacheable):
|
|
|
740
745
|
or self._bool_format_id is not None
|
|
741
746
|
):
|
|
742
747
|
return self._custom_format()
|
|
743
|
-
return str(self.value)
|
|
748
|
+
return str(self.value).upper() if isinstance(self.value, bool) else str(self.value)
|
|
744
749
|
|
|
745
750
|
@property
|
|
746
751
|
def style(self) -> Style | None:
|
|
@@ -872,20 +877,16 @@ class Cell(CellStorageFlags, Cacheable):
|
|
|
872
877
|
storage_flags._text_style_id = unpack("<i", buffer[offset : offset + 4])[0]
|
|
873
878
|
offset += 4
|
|
874
879
|
if flags & 0x80:
|
|
875
|
-
#
|
|
880
|
+
# cond_style_id skipped
|
|
876
881
|
offset += 4
|
|
877
|
-
#
|
|
878
|
-
# storage_flags._cond_rule_style_id = unpack("<i", buffer[offset : offset + 4])[0]
|
|
879
|
-
# offset += 4
|
|
882
|
+
# Skip flag 0x100 (cond_rule_style_id)
|
|
880
883
|
if flags & 0x200:
|
|
881
884
|
storage_flags._formula_id = unpack("<i", buffer[offset : offset + 4])[0]
|
|
882
885
|
offset += 4
|
|
883
886
|
if flags & 0x400:
|
|
884
887
|
storage_flags._control_id = unpack("<i", buffer[offset : offset + 4])[0]
|
|
885
888
|
offset += 4
|
|
886
|
-
#
|
|
887
|
-
# storage_flags._formula_error_id = unpack("<i", buffer[offset : offset + 4])[0]
|
|
888
|
-
# offset += 4
|
|
889
|
+
# Skip flag 0x800 (formula_error_id)
|
|
889
890
|
if flags & 0x1000:
|
|
890
891
|
storage_flags._suggest_id = unpack("<i", buffer[offset : offset + 4])[0]
|
|
891
892
|
offset += 4
|
|
@@ -909,12 +910,7 @@ class Cell(CellStorageFlags, Cacheable):
|
|
|
909
910
|
if flags & 0x40000:
|
|
910
911
|
storage_flags._bool_format_id = unpack("<i", buffer[offset : offset + 4])[0]
|
|
911
912
|
offset += 4
|
|
912
|
-
#
|
|
913
|
-
# cstorage_flags._omment_id = unpack("<i", buffer[offset : offset + 4])[0]
|
|
914
|
-
# offset += 4
|
|
915
|
-
# if flags & 0x100000:
|
|
916
|
-
# storage_flags._import_warning_id = unpack("<i", buffer[offset : offset + 4])[0]
|
|
917
|
-
# offset += 4
|
|
913
|
+
# Skip 0x80000 (comment_id) and 0x100000 (import_warning_id)
|
|
918
914
|
|
|
919
915
|
cell_type = buffer[1]
|
|
920
916
|
if cell_type == TSTArchives.genericCellType:
|
|
@@ -1032,7 +1028,6 @@ class Cell(CellStorageFlags, Cacheable):
|
|
|
1032
1028
|
flags = 4
|
|
1033
1029
|
length += 8
|
|
1034
1030
|
cell_type = TSTArchives.dateCellType
|
|
1035
|
-
# date_delta = self._value.astimezone() - EPOCH
|
|
1036
1031
|
if self._value.tzinfo is None:
|
|
1037
1032
|
date_delta = self._value - EPOCH
|
|
1038
1033
|
else:
|
|
@@ -1104,7 +1099,6 @@ class Cell(CellStorageFlags, Cacheable):
|
|
|
1104
1099
|
length += 4
|
|
1105
1100
|
storage += pack("<i", self._num_format_id)
|
|
1106
1101
|
storage[6] |= 1
|
|
1107
|
-
# storage[6:8] = pack("<h", 1)
|
|
1108
1102
|
if self._currency_format_id is not None:
|
|
1109
1103
|
flags |= 0x4000
|
|
1110
1104
|
length += 4
|
|
@@ -1560,8 +1554,8 @@ class MergedCell(Cell):
|
|
|
1560
1554
|
def _pack_decimal128(value: float) -> bytearray:
|
|
1561
1555
|
buffer = bytearray(16)
|
|
1562
1556
|
exp = math.floor(math.log10(math.e) * math.log(abs(value))) if value != 0.0 else 0
|
|
1563
|
-
exp +=
|
|
1564
|
-
mantissa = abs(int(value / math.pow(10, exp -
|
|
1557
|
+
exp += DECIMAL128_BIAS - 16
|
|
1558
|
+
mantissa = abs(int(value / math.pow(10, exp - DECIMAL128_BIAS)))
|
|
1565
1559
|
buffer[15] |= exp >> 7
|
|
1566
1560
|
buffer[14] |= (exp & 0x7F) << 1
|
|
1567
1561
|
i = 0
|
|
@@ -1575,7 +1569,7 @@ def _pack_decimal128(value: float) -> bytearray:
|
|
|
1575
1569
|
|
|
1576
1570
|
|
|
1577
1571
|
def _unpack_decimal128(buffer: bytearray) -> float:
|
|
1578
|
-
exp = (((buffer[15] & 0x7F) << 7) | (buffer[14] >> 1)) -
|
|
1572
|
+
exp = (((buffer[15] & 0x7F) << 7) | (buffer[14] >> 1)) - DECIMAL128_BIAS
|
|
1579
1573
|
mantissa = buffer[14] & 1
|
|
1580
1574
|
for i in range(13, -1, -1):
|
|
1581
1575
|
mantissa = mantissa * 256 + buffer[i]
|
|
@@ -1756,16 +1750,6 @@ def _decode_number_format(number_format, value, name): # noqa: PLR0912
|
|
|
1756
1750
|
int_pad = None
|
|
1757
1751
|
int_width = num_integers
|
|
1758
1752
|
|
|
1759
|
-
# value_1 = str(value).split(".")[0]
|
|
1760
|
-
# value_2 = sigfig(str(value).split(".")[1], sigfig=MAX_SIGNIFICANT_DIGITS, warn=False)
|
|
1761
|
-
# int_pad_space_as_zero = (
|
|
1762
|
-
# num_integers > 0
|
|
1763
|
-
# and num_decimals > 0
|
|
1764
|
-
# and int_pad == CellPadding.SPACE
|
|
1765
|
-
# and dec_pad is None
|
|
1766
|
-
# and num_integers > len(value_1)
|
|
1767
|
-
# and num_decimals > len(value_2)
|
|
1768
|
-
# )
|
|
1769
1753
|
int_pad_space_as_zero = False
|
|
1770
1754
|
|
|
1771
1755
|
# Formatting integer zero:
|
|
@@ -1779,16 +1763,11 @@ def _decode_number_format(number_format, value, name): # noqa: PLR0912
|
|
|
1779
1763
|
formatted_value = "".rjust(int_width)
|
|
1780
1764
|
elif integer == 0 and int_pad is None and dec_pad == CellPadding.SPACE:
|
|
1781
1765
|
formatted_value = ""
|
|
1782
|
-
elif (
|
|
1766
|
+
elif (integer == 0 and int_pad == CellPadding.SPACE and dec_pad is not None) or (
|
|
1783
1767
|
integer == 0
|
|
1784
1768
|
and int_pad == CellPadding.SPACE
|
|
1785
|
-
and dec_pad is
|
|
1786
|
-
|
|
1787
|
-
integer == 0
|
|
1788
|
-
and int_pad == CellPadding.SPACE
|
|
1789
|
-
and dec_pad is None
|
|
1790
|
-
and len(str(decimal)) > num_decimals
|
|
1791
|
-
)
|
|
1769
|
+
and dec_pad is None
|
|
1770
|
+
and len(str(decimal)) > num_decimals
|
|
1792
1771
|
):
|
|
1793
1772
|
formatted_value = "".rjust(int_width)
|
|
1794
1773
|
elif int_pad_space_as_zero or int_pad == CellPadding.ZERO:
|
|
@@ -2016,159 +1995,6 @@ def _auto_units(cell_value, number_format):
|
|
|
2016
1995
|
return unit_smallest, unit_largest
|
|
2017
1996
|
|
|
2018
1997
|
|
|
2019
|
-
# Cell reference conversion from https://github.com/jmcnamara/XlsxWriter
|
|
2020
|
-
# Copyright (c) 2013-2021, John McNamara <jmcnamara@cpan.org>
|
|
2021
|
-
range_parts = re.compile(r"(\$?)([A-Z]{1,3})(\$?)(\d+)")
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
def xl_cell_to_rowcol(cell_str: str) -> tuple:
|
|
2025
|
-
"""
|
|
2026
|
-
Convert a cell reference in A1 notation to a zero indexed row and column.
|
|
2027
|
-
|
|
2028
|
-
Parameters
|
|
2029
|
-
----------
|
|
2030
|
-
cell_str: str
|
|
2031
|
-
A1 notation cell reference
|
|
2032
|
-
|
|
2033
|
-
Returns
|
|
2034
|
-
-------
|
|
2035
|
-
row, col: int, int
|
|
2036
|
-
Cell row and column numbers (zero indexed).
|
|
2037
|
-
|
|
2038
|
-
"""
|
|
2039
|
-
if not cell_str:
|
|
2040
|
-
return 0, 0
|
|
2041
|
-
|
|
2042
|
-
match = range_parts.match(cell_str)
|
|
2043
|
-
if not match:
|
|
2044
|
-
msg = f"invalid cell reference {cell_str}"
|
|
2045
|
-
raise IndexError(msg)
|
|
2046
|
-
|
|
2047
|
-
col_str = match.group(2)
|
|
2048
|
-
row_str = match.group(4)
|
|
2049
|
-
|
|
2050
|
-
# Convert base26 column string to number.
|
|
2051
|
-
col = 0
|
|
2052
|
-
for expn, char in enumerate(reversed(col_str)):
|
|
2053
|
-
col += (ord(char) - ord("A") + 1) * (26**expn)
|
|
2054
|
-
|
|
2055
|
-
# Convert 1-index to zero-index
|
|
2056
|
-
row = int(row_str) - 1
|
|
2057
|
-
col -= 1
|
|
2058
|
-
|
|
2059
|
-
return row, col
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
def xl_range(first_row, first_col, last_row, last_col):
|
|
2063
|
-
"""
|
|
2064
|
-
Convert zero indexed row and col cell references to a A1:B1 range string.
|
|
2065
|
-
|
|
2066
|
-
Parameters
|
|
2067
|
-
----------
|
|
2068
|
-
first_row: int
|
|
2069
|
-
The first cell row.
|
|
2070
|
-
first_col: int
|
|
2071
|
-
The first cell column.
|
|
2072
|
-
last_row: int
|
|
2073
|
-
The last cell row.
|
|
2074
|
-
last_col: int
|
|
2075
|
-
The last cell column.
|
|
2076
|
-
|
|
2077
|
-
Returns
|
|
2078
|
-
-------
|
|
2079
|
-
str:
|
|
2080
|
-
A1:B1 style range string.
|
|
2081
|
-
|
|
2082
|
-
"""
|
|
2083
|
-
range1 = xl_rowcol_to_cell(first_row, first_col)
|
|
2084
|
-
range2 = xl_rowcol_to_cell(last_row, last_col)
|
|
2085
|
-
|
|
2086
|
-
if range1 == range2:
|
|
2087
|
-
return range1
|
|
2088
|
-
return range1 + ":" + range2
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
def xl_rowcol_to_cell(row, col, row_abs=False, col_abs=False):
|
|
2092
|
-
"""
|
|
2093
|
-
Convert a zero indexed row and column cell reference to a A1 style string.
|
|
2094
|
-
|
|
2095
|
-
Parameters
|
|
2096
|
-
----------
|
|
2097
|
-
row: int
|
|
2098
|
-
The cell row.
|
|
2099
|
-
col: int
|
|
2100
|
-
The cell column.
|
|
2101
|
-
row_abs: bool
|
|
2102
|
-
If ``True``, make the row absolute.
|
|
2103
|
-
col_abs: bool
|
|
2104
|
-
If ``True``, make the column absolute.
|
|
2105
|
-
|
|
2106
|
-
Returns
|
|
2107
|
-
-------
|
|
2108
|
-
str:
|
|
2109
|
-
A1 style string.
|
|
2110
|
-
|
|
2111
|
-
"""
|
|
2112
|
-
if row < 0:
|
|
2113
|
-
msg = f"row reference {row} below zero"
|
|
2114
|
-
raise IndexError(msg)
|
|
2115
|
-
|
|
2116
|
-
if col < 0:
|
|
2117
|
-
msg = f"column reference {col} below zero"
|
|
2118
|
-
raise IndexError(msg)
|
|
2119
|
-
|
|
2120
|
-
row += 1 # Change to 1-index.
|
|
2121
|
-
row_abs = "$" if row_abs else ""
|
|
2122
|
-
|
|
2123
|
-
col_str = xl_col_to_name(col, col_abs)
|
|
2124
|
-
|
|
2125
|
-
return col_str + row_abs + str(row)
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
def xl_col_to_name(col, col_abs=False):
|
|
2129
|
-
"""
|
|
2130
|
-
Convert a zero indexed column cell reference to a string.
|
|
2131
|
-
|
|
2132
|
-
Parameters
|
|
2133
|
-
----------
|
|
2134
|
-
col: int
|
|
2135
|
-
The column number (zero indexed).
|
|
2136
|
-
col_abs: bool, default: False
|
|
2137
|
-
If ``True``, make the column absolute.
|
|
2138
|
-
|
|
2139
|
-
Returns
|
|
2140
|
-
-------
|
|
2141
|
-
str:
|
|
2142
|
-
Column in A1 notation.
|
|
2143
|
-
|
|
2144
|
-
"""
|
|
2145
|
-
if col < 0:
|
|
2146
|
-
msg = f"column reference {col} below zero"
|
|
2147
|
-
raise IndexError(msg)
|
|
2148
|
-
|
|
2149
|
-
col += 1 # Change to 1-index.
|
|
2150
|
-
col_str = ""
|
|
2151
|
-
col_abs = "$" if col_abs else ""
|
|
2152
|
-
|
|
2153
|
-
while col:
|
|
2154
|
-
# Set remainder from 1 .. 26
|
|
2155
|
-
remainder = col % 26
|
|
2156
|
-
|
|
2157
|
-
if remainder == 0:
|
|
2158
|
-
remainder = 26
|
|
2159
|
-
|
|
2160
|
-
# Convert the remainder to a character.
|
|
2161
|
-
col_letter = chr(ord("A") + remainder - 1)
|
|
2162
|
-
|
|
2163
|
-
# Accumulate the column letters, right to left.
|
|
2164
|
-
col_str = col_letter + col_str
|
|
2165
|
-
|
|
2166
|
-
# Get the next order of magnitude.
|
|
2167
|
-
col = int((col - 1) / 26)
|
|
2168
|
-
|
|
2169
|
-
return col_abs + col_str
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
1998
|
@dataclass()
|
|
2173
1999
|
class Formatting:
|
|
2174
2000
|
allow_none: bool = False
|
numbers_parser/constants.py
CHANGED
|
@@ -12,16 +12,16 @@ except ImportError: # pragma: nocover
|
|
|
12
12
|
from importlib_resources import files
|
|
13
13
|
|
|
14
14
|
__all__ = [
|
|
15
|
-
"CellType",
|
|
16
|
-
"PaddingType",
|
|
17
15
|
"CellPadding",
|
|
16
|
+
"CellType",
|
|
17
|
+
"ControlFormattingType",
|
|
18
18
|
"DurationStyle",
|
|
19
19
|
"DurationUnits",
|
|
20
20
|
"FormatType",
|
|
21
21
|
"FormattingType",
|
|
22
|
-
"NegativeNumberStyle",
|
|
23
22
|
"FractionAccuracy",
|
|
24
|
-
"
|
|
23
|
+
"NegativeNumberStyle",
|
|
24
|
+
"PaddingType",
|
|
25
25
|
]
|
|
26
26
|
|
|
27
27
|
DEFAULT_DOCUMENT = files("numbers_parser") / "data" / "empty.numbers"
|
|
@@ -51,6 +51,8 @@ DEFAULT_DATETIME_FORMAT = "dd MMM YYY HH:MM"
|
|
|
51
51
|
CHECKBOX_FALSE_VALUE = "☐"
|
|
52
52
|
CHECKBOX_TRUE_VALUE = "☑"
|
|
53
53
|
STAR_RATING_VALUE = "★"
|
|
54
|
+
OPERATOR_PRECEDENCE = {"%": 6, "^": 5, "×": 4, "*": 4, "/": 4, "÷": 4, "+": 3, "-": 3, "&": 2}
|
|
55
|
+
|
|
54
56
|
|
|
55
57
|
# Numbers limits
|
|
56
58
|
MAX_TILE_SIZE = 256
|
|
@@ -69,6 +71,7 @@ EPOCH = datetime(2001, 1, 1) # noqa: DTZ001
|
|
|
69
71
|
SECONDS_IN_HOUR = 60 * 60
|
|
70
72
|
SECONDS_IN_DAY = SECONDS_IN_HOUR * 24
|
|
71
73
|
SECONDS_IN_WEEK = SECONDS_IN_DAY * 7
|
|
74
|
+
DECIMAL128_BIAS = 0x1820
|
|
72
75
|
|
|
73
76
|
# File format enumerations
|
|
74
77
|
DECIMAL_PLACES_AUTO = 253
|
|
@@ -91,6 +94,7 @@ SUPPORTED_NUMBERS_VERSIONS = [
|
|
|
91
94
|
"14.1",
|
|
92
95
|
"14.2",
|
|
93
96
|
"14.3",
|
|
97
|
+
"14.4",
|
|
94
98
|
]
|
|
95
99
|
|
|
96
100
|
|
numbers_parser/document.py
CHANGED
|
@@ -17,8 +17,6 @@ from numbers_parser.cell import (
|
|
|
17
17
|
Style,
|
|
18
18
|
TextCell,
|
|
19
19
|
UnsupportedWarning,
|
|
20
|
-
xl_cell_to_rowcol,
|
|
21
|
-
xl_range,
|
|
22
20
|
)
|
|
23
21
|
from numbers_parser.constants import (
|
|
24
22
|
CUSTOM_FORMATTING_ALLOWED_CELLS,
|
|
@@ -33,6 +31,7 @@ from numbers_parser.constants import (
|
|
|
33
31
|
from numbers_parser.containers import ItemsList
|
|
34
32
|
from numbers_parser.model import _NumbersModel
|
|
35
33
|
from numbers_parser.numbers_cache import Cacheable
|
|
34
|
+
from numbers_parser.xrefs import xl_cell_to_rowcol, xl_range
|
|
36
35
|
|
|
37
36
|
if TYPE_CHECKING: # pragma: nocover
|
|
38
37
|
from collections.abc import Iterator
|
|
@@ -379,6 +378,8 @@ class Sheet:
|
|
|
379
378
|
y: float | None = None,
|
|
380
379
|
num_rows: int | None = DEFAULT_ROW_COUNT,
|
|
381
380
|
num_cols: int | None = DEFAULT_COLUMN_COUNT,
|
|
381
|
+
num_header_rows: int | None = 1,
|
|
382
|
+
num_header_cols: int | None = 1,
|
|
382
383
|
) -> Table:
|
|
383
384
|
"""
|
|
384
385
|
Add a new table to the current sheet.
|
|
@@ -411,6 +412,10 @@ class Sheet:
|
|
|
411
412
|
The number of rows for the new table.
|
|
412
413
|
num_cols: int, optional, default: 10
|
|
413
414
|
The number of columns for the new table.
|
|
415
|
+
num_header_rows: int, optional, default: 1
|
|
416
|
+
The number of header rows for the new table.
|
|
417
|
+
num_header_cols: int, optional, default: 1
|
|
418
|
+
The number of header columns for the new table.
|
|
414
419
|
|
|
415
420
|
Returns
|
|
416
421
|
-------
|
|
@@ -423,7 +428,16 @@ class Sheet:
|
|
|
423
428
|
|
|
424
429
|
"""
|
|
425
430
|
from_table_id = self._tables[-1]._table_id
|
|
426
|
-
return self._add_table(
|
|
431
|
+
return self._add_table(
|
|
432
|
+
table_name,
|
|
433
|
+
from_table_id,
|
|
434
|
+
x,
|
|
435
|
+
y,
|
|
436
|
+
num_rows,
|
|
437
|
+
num_cols,
|
|
438
|
+
num_header_rows,
|
|
439
|
+
num_header_cols,
|
|
440
|
+
)
|
|
427
441
|
|
|
428
442
|
def _add_table(
|
|
429
443
|
self,
|
|
@@ -433,6 +447,8 @@ class Sheet:
|
|
|
433
447
|
y,
|
|
434
448
|
num_rows,
|
|
435
449
|
num_cols,
|
|
450
|
+
num_header_rows,
|
|
451
|
+
num_header_cols,
|
|
436
452
|
) -> object:
|
|
437
453
|
if table_name is not None:
|
|
438
454
|
if table_name in self._tables:
|
|
@@ -452,6 +468,8 @@ class Sheet:
|
|
|
452
468
|
y,
|
|
453
469
|
num_rows,
|
|
454
470
|
num_cols,
|
|
471
|
+
num_header_rows,
|
|
472
|
+
num_header_cols,
|
|
455
473
|
)
|
|
456
474
|
self._tables.append(Table(self._model, new_table_id))
|
|
457
475
|
return self._tables[-1]
|
|
@@ -968,6 +986,11 @@ class Table(Cacheable):
|
|
|
968
986
|
self._data[row][col]._model = self._model
|
|
969
987
|
self._data[row][col]._set_merge(merge_cells.get((row, col)))
|
|
970
988
|
|
|
989
|
+
if row < self._model.num_header_rows(self._table_id) or col < self._model.num_header_cols(
|
|
990
|
+
self._table_id,
|
|
991
|
+
):
|
|
992
|
+
self._model.name_ref_cache.mark_dirty()
|
|
993
|
+
|
|
971
994
|
if style is not None:
|
|
972
995
|
self.set_cell_style(row, col, style)
|
|
973
996
|
|