numbers-parser 4.10.1__py3-none-any.whl → 4.10.3__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 +8 -23
- numbers_parser/cell.py +1152 -165
- numbers_parser/constants.py +8 -0
- numbers_parser/containers.py +1 -1
- numbers_parser/document.py +17 -15
- numbers_parser/model.py +89 -263
- {numbers_parser-4.10.1.dist-info → numbers_parser-4.10.3.dist-info}/METADATA +15 -12
- {numbers_parser-4.10.1.dist-info → numbers_parser-4.10.3.dist-info}/RECORD +11 -12
- numbers_parser/cell_storage.py +0 -927
- {numbers_parser-4.10.1.dist-info → numbers_parser-4.10.3.dist-info}/LICENSE.rst +0 -0
- {numbers_parser-4.10.1.dist-info → numbers_parser-4.10.3.dist-info}/WHEEL +0 -0
- {numbers_parser-4.10.1.dist-info → numbers_parser-4.10.3.dist-info}/entry_points.txt +0 -0
numbers_parser/model.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import math
|
|
2
1
|
import re
|
|
3
2
|
from array import array
|
|
4
3
|
from collections import defaultdict
|
|
@@ -6,7 +5,6 @@ from hashlib import sha1
|
|
|
6
5
|
from pathlib import Path
|
|
7
6
|
from struct import pack
|
|
8
7
|
from typing import Dict, List, Tuple, Union
|
|
9
|
-
from warnings import warn
|
|
10
8
|
|
|
11
9
|
from numbers_parser.bullets import (
|
|
12
10
|
BULLET_CONVERSION,
|
|
@@ -16,32 +14,24 @@ from numbers_parser.bullets import (
|
|
|
16
14
|
from numbers_parser.cell import (
|
|
17
15
|
RGB,
|
|
18
16
|
Alignment,
|
|
19
|
-
BoolCell,
|
|
20
17
|
Border,
|
|
21
18
|
BorderType,
|
|
19
|
+
Cell,
|
|
22
20
|
CustomFormatting,
|
|
23
|
-
DateCell,
|
|
24
|
-
DurationCell,
|
|
25
|
-
EmptyCell,
|
|
26
21
|
Formatting,
|
|
27
22
|
FormattingType,
|
|
28
23
|
HorizontalJustification,
|
|
29
24
|
MergeAnchor,
|
|
30
25
|
MergedCell,
|
|
31
26
|
MergeReference,
|
|
32
|
-
NumberCell,
|
|
33
27
|
PaddingType,
|
|
34
|
-
RichTextCell,
|
|
35
28
|
Style,
|
|
36
|
-
TextCell,
|
|
37
29
|
VerticalJustification,
|
|
38
30
|
xl_col_to_name,
|
|
39
31
|
xl_rowcol_to_cell,
|
|
40
32
|
)
|
|
41
|
-
from numbers_parser.cell_storage import CellStorage
|
|
42
33
|
from numbers_parser.constants import (
|
|
43
34
|
ALLOWED_FORMATTING_PARAMETERS,
|
|
44
|
-
CURRENCY_CELL_TYPE,
|
|
45
35
|
CUSTOM_FORMAT_TYPE_MAP,
|
|
46
36
|
CUSTOM_TEXT_PLACEHOLDER,
|
|
47
37
|
DEFAULT_COLUMN_WIDTH,
|
|
@@ -53,15 +43,15 @@ from numbers_parser.constants import (
|
|
|
53
43
|
DEFAULT_TEXT_WRAP,
|
|
54
44
|
DEFAULT_TILE_SIZE,
|
|
55
45
|
DOCUMENT_ID,
|
|
56
|
-
EPOCH,
|
|
57
46
|
FORMAT_TYPE_MAP,
|
|
58
47
|
MAX_TILE_SIZE,
|
|
59
48
|
PACKAGE_ID,
|
|
60
49
|
CellInteractionType,
|
|
61
50
|
FormatType,
|
|
51
|
+
OwnerKind,
|
|
62
52
|
)
|
|
63
53
|
from numbers_parser.containers import ObjectStore
|
|
64
|
-
from numbers_parser.exceptions import UnsupportedError
|
|
54
|
+
from numbers_parser.exceptions import UnsupportedError
|
|
65
55
|
from numbers_parser.formula import TableFormulas
|
|
66
56
|
from numbers_parser.generated import TNArchives_pb2 as TNArchives
|
|
67
57
|
from numbers_parser.generated import TSCEArchives_pb2 as TSCEArchives
|
|
@@ -106,9 +96,11 @@ class MergeCells:
|
|
|
106
96
|
self._references[(row, col)] = MergeAnchor(size)
|
|
107
97
|
|
|
108
98
|
def is_merge_reference(self, row_col: Tuple) -> bool:
|
|
99
|
+
# defaultdict will default this to False for missing entries
|
|
109
100
|
return isinstance(self._references[row_col], MergeReference)
|
|
110
101
|
|
|
111
102
|
def is_merge_anchor(self, row_col: Tuple) -> bool:
|
|
103
|
+
# defaultdict will default this to False for missing entries
|
|
112
104
|
return isinstance(self._references[row_col], MergeAnchor)
|
|
113
105
|
|
|
114
106
|
def get(self, row_col: Tuple) -> Union[MergeAnchor, MergeReference]:
|
|
@@ -123,9 +115,6 @@ class MergeCells:
|
|
|
123
115
|
def merge_cells(self):
|
|
124
116
|
return [k for k, v in self._references.items() if self.is_merge_anchor(k)]
|
|
125
117
|
|
|
126
|
-
def __len__(self):
|
|
127
|
-
return len(self._references.keys())
|
|
128
|
-
|
|
129
118
|
|
|
130
119
|
class DataLists(Cacheable):
|
|
131
120
|
"""Model for TST.DataList with caching and key generation for new values."""
|
|
@@ -226,6 +215,7 @@ class _NumbersModel(Cacheable):
|
|
|
226
215
|
self._control_specs = DataLists(self, "control_cell_spec_table", "cell_spec")
|
|
227
216
|
self._table_data = {}
|
|
228
217
|
self._styles = None
|
|
218
|
+
self._images = {}
|
|
229
219
|
self._custom_formats = None
|
|
230
220
|
self._custom_format_archives = None
|
|
231
221
|
self._custom_format_ids = None
|
|
@@ -603,6 +593,7 @@ class _NumbersModel(Cacheable):
|
|
|
603
593
|
formula_owner_ids = self.find_refs("FormulaOwnerDependenciesArchive")
|
|
604
594
|
for dependency_id in formula_owner_ids: # pragma: no branch
|
|
605
595
|
obj = self.objects[dependency_id]
|
|
596
|
+
# if obj.owner_kind == OwnerKind.HAUNTED_OWNER:
|
|
606
597
|
if obj.HasField("base_owner_uid") and obj.HasField(
|
|
607
598
|
"formula_owner_uid"
|
|
608
599
|
): # pragma: no branch
|
|
@@ -653,17 +644,20 @@ class _NumbersModel(Cacheable):
|
|
|
653
644
|
owner_id_map = self.owner_id_map()
|
|
654
645
|
table_base_id = self.table_base_id(table_id)
|
|
655
646
|
|
|
656
|
-
|
|
657
|
-
for
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
647
|
+
formula_table_ids = self.find_refs("FormulaOwnerDependenciesArchive")
|
|
648
|
+
for formula_id in formula_table_ids:
|
|
649
|
+
dependencies = self.objects[formula_id]
|
|
650
|
+
if dependencies.owner_kind != OwnerKind.MERGE_OWNER:
|
|
651
|
+
continue
|
|
652
|
+
for record in dependencies.range_dependencies.back_dependency:
|
|
653
|
+
to_owner_id = record.internal_range_reference.owner_id
|
|
654
|
+
if owner_id_map[to_owner_id] == table_base_id:
|
|
655
|
+
record_range = record.internal_range_reference.range
|
|
656
|
+
row_start = record_range.top_left_row
|
|
657
|
+
row_end = record_range.bottom_right_row
|
|
658
|
+
col_start = record_range.top_left_column
|
|
659
|
+
col_end = record_range.bottom_right_column
|
|
660
|
+
|
|
667
661
|
size = (row_end - row_start + 1, col_end - col_start + 1)
|
|
668
662
|
for row in range(row_start, row_end + 1):
|
|
669
663
|
for col in range(col_start, col_end + 1):
|
|
@@ -853,8 +847,6 @@ class _NumbersModel(Cacheable):
|
|
|
853
847
|
|
|
854
848
|
def recalculate_merged_cells(self, table_id: int):
|
|
855
849
|
merge_cells = self.merge_cells(table_id)
|
|
856
|
-
if len(merge_cells) == 0:
|
|
857
|
-
return
|
|
858
850
|
|
|
859
851
|
merge_map_id, merge_map = self.objects.create_object_from_dict(
|
|
860
852
|
"CalculationEngine", {}, TSTArchives.MergeRegionMapArchive
|
|
@@ -884,7 +876,7 @@ class _NumbersModel(Cacheable):
|
|
|
884
876
|
current_offset = 0
|
|
885
877
|
|
|
886
878
|
for col in range(len(data[row])):
|
|
887
|
-
buffer =
|
|
879
|
+
buffer = data[row][col]._to_buffer()
|
|
888
880
|
if buffer is not None:
|
|
889
881
|
cell_storage += buffer
|
|
890
882
|
# Always use wide offsets
|
|
@@ -1233,8 +1225,6 @@ class _NumbersModel(Cacheable):
|
|
|
1233
1225
|
)
|
|
1234
1226
|
)
|
|
1235
1227
|
|
|
1236
|
-
data = [[EmptyCell(row, col) for col in range(num_cols)] for row in range(num_rows)]
|
|
1237
|
-
|
|
1238
1228
|
row_headers_id, _ = self.objects.create_object_from_dict(
|
|
1239
1229
|
"Index/Tables/HeaderStorageBucket-{}",
|
|
1240
1230
|
{"bucketHashFunction": 1},
|
|
@@ -1248,6 +1238,10 @@ class _NumbersModel(Cacheable):
|
|
|
1248
1238
|
TSPMessages.Reference(identifier=row_headers_id)
|
|
1249
1239
|
)
|
|
1250
1240
|
|
|
1241
|
+
data = [
|
|
1242
|
+
[Cell._empty_cell(table_model_id, row, col, self) for col in range(num_cols)]
|
|
1243
|
+
for row in range(num_rows)
|
|
1244
|
+
]
|
|
1251
1245
|
self.recalculate_table_data(table_model_id, data)
|
|
1252
1246
|
|
|
1253
1247
|
table_info_id, table_info = self.objects.create_object_from_dict(
|
|
@@ -1546,17 +1540,22 @@ class _NumbersModel(Cacheable):
|
|
|
1546
1540
|
|
|
1547
1541
|
def add_cell_style(self, style: Style) -> int:
|
|
1548
1542
|
if style.bg_image is not None:
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1543
|
+
digest = sha1(style.bg_image.data).digest()
|
|
1544
|
+
if digest in self._images:
|
|
1545
|
+
image_id = self._images[digest]
|
|
1546
|
+
else:
|
|
1547
|
+
datas = self.objects[PACKAGE_ID].datas
|
|
1548
|
+
image_id = self.next_image_identifier()
|
|
1549
|
+
datas.append(
|
|
1550
|
+
TSPArchiveMessages.DataInfo(
|
|
1551
|
+
identifier=image_id,
|
|
1552
|
+
digest=digest,
|
|
1553
|
+
preferred_file_name=style.bg_image.filename,
|
|
1554
|
+
file_name=style.bg_image.filename,
|
|
1555
|
+
materialized_length=len(style.bg_image.data),
|
|
1556
|
+
)
|
|
1558
1557
|
)
|
|
1559
|
-
|
|
1558
|
+
self._images[digest] = image_id
|
|
1560
1559
|
color_attrs = {
|
|
1561
1560
|
"cell_fill": {
|
|
1562
1561
|
"image": {
|
|
@@ -1618,16 +1617,16 @@ class _NumbersModel(Cacheable):
|
|
|
1618
1617
|
|
|
1619
1618
|
return cell_style_id
|
|
1620
1619
|
|
|
1621
|
-
def text_style_object_id(self,
|
|
1622
|
-
if
|
|
1620
|
+
def text_style_object_id(self, cell: Cell) -> int:
|
|
1621
|
+
if cell._text_style_id is None:
|
|
1623
1622
|
return None
|
|
1624
|
-
entry = self._table_styles.lookup_value(
|
|
1623
|
+
entry = self._table_styles.lookup_value(cell._table_id, cell._text_style_id)
|
|
1625
1624
|
return entry.reference.identifier
|
|
1626
1625
|
|
|
1627
|
-
def cell_style_object_id(self,
|
|
1628
|
-
if
|
|
1626
|
+
def cell_style_object_id(self, cell: Cell) -> int:
|
|
1627
|
+
if cell._cell_style_id is None:
|
|
1629
1628
|
return None
|
|
1630
|
-
entry = self._table_styles.lookup_value(
|
|
1629
|
+
entry = self._table_styles.lookup_value(cell._table_id, cell._cell_style_id)
|
|
1631
1630
|
return entry.reference.identifier
|
|
1632
1631
|
|
|
1633
1632
|
def custom_style_name(self) -> str:
|
|
@@ -1683,166 +1682,10 @@ class _NumbersModel(Cacheable):
|
|
|
1683
1682
|
else:
|
|
1684
1683
|
return "Custom Format 1"
|
|
1685
1684
|
|
|
1686
|
-
def pack_cell_storage( # noqa: PLR0912, PLR0915
|
|
1687
|
-
self, table_id: int, data: List, row: int, col: int
|
|
1688
|
-
) -> bytearray:
|
|
1689
|
-
"""Create a storage buffer for a cell using v5 (modern) layout."""
|
|
1690
|
-
cell = data[row][col]
|
|
1691
|
-
if cell._style is not None:
|
|
1692
|
-
if cell._style._text_style_obj_id is not None:
|
|
1693
|
-
cell._storage.text_style_id = self._table_styles.lookup_key(
|
|
1694
|
-
cell._table_id,
|
|
1695
|
-
TSPMessages.Reference(identifier=cell._style._text_style_obj_id),
|
|
1696
|
-
)
|
|
1697
|
-
self.add_component_reference(
|
|
1698
|
-
cell._style._text_style_obj_id,
|
|
1699
|
-
parent_id=self._table_styles.id(cell._table_id),
|
|
1700
|
-
)
|
|
1701
|
-
|
|
1702
|
-
if cell._style._cell_style_obj_id is not None:
|
|
1703
|
-
cell._storage.cell_style_id = self._table_styles.lookup_key(
|
|
1704
|
-
cell._table_id,
|
|
1705
|
-
TSPMessages.Reference(identifier=cell._style._cell_style_obj_id),
|
|
1706
|
-
)
|
|
1707
|
-
self.add_component_reference(
|
|
1708
|
-
cell._style._cell_style_obj_id,
|
|
1709
|
-
parent_id=self._table_styles.id(cell._table_id),
|
|
1710
|
-
)
|
|
1711
|
-
|
|
1712
|
-
length = 12
|
|
1713
|
-
if isinstance(cell, NumberCell):
|
|
1714
|
-
flags = 1
|
|
1715
|
-
length += 16
|
|
1716
|
-
if cell._storage.is_currency:
|
|
1717
|
-
cell_type = CURRENCY_CELL_TYPE
|
|
1718
|
-
else:
|
|
1719
|
-
cell_type = TSTArchives.numberCellType
|
|
1720
|
-
value = pack_decimal128(cell.value)
|
|
1721
|
-
elif isinstance(cell, TextCell):
|
|
1722
|
-
flags = 8
|
|
1723
|
-
length += 4
|
|
1724
|
-
cell_type = TSTArchives.textCellType
|
|
1725
|
-
value = pack("<i", self.table_string_key(table_id, cell.value))
|
|
1726
|
-
elif isinstance(cell, DateCell):
|
|
1727
|
-
flags = 4
|
|
1728
|
-
length += 8
|
|
1729
|
-
cell_type = TSTArchives.dateCellType
|
|
1730
|
-
date_delta = cell.value - EPOCH
|
|
1731
|
-
value = pack("<d", float(date_delta.total_seconds()))
|
|
1732
|
-
elif isinstance(cell, BoolCell):
|
|
1733
|
-
flags = 2
|
|
1734
|
-
length += 8
|
|
1735
|
-
cell_type = TSTArchives.boolCellType
|
|
1736
|
-
value = pack("<d", float(cell.value))
|
|
1737
|
-
elif isinstance(cell, DurationCell):
|
|
1738
|
-
flags = 2
|
|
1739
|
-
length += 8
|
|
1740
|
-
cell_type = TSTArchives.durationCellType
|
|
1741
|
-
value = value = pack("<d", float(cell.value.total_seconds()))
|
|
1742
|
-
elif isinstance(cell, EmptyCell):
|
|
1743
|
-
if cell._style is not None or cell._storage is not None:
|
|
1744
|
-
flags = 0
|
|
1745
|
-
cell_type = TSTArchives.emptyCellValueType
|
|
1746
|
-
value = b""
|
|
1747
|
-
else:
|
|
1748
|
-
return None
|
|
1749
|
-
elif isinstance(cell, MergedCell):
|
|
1750
|
-
return None
|
|
1751
|
-
elif isinstance(cell, RichTextCell):
|
|
1752
|
-
flags = 0
|
|
1753
|
-
length += 4
|
|
1754
|
-
cell_type = TSTArchives.automaticCellType
|
|
1755
|
-
value = pack("<i", cell._storage.rich_id)
|
|
1756
|
-
else:
|
|
1757
|
-
data_type = type(cell).__name__
|
|
1758
|
-
table_name = self.table_name(table_id)
|
|
1759
|
-
warn(
|
|
1760
|
-
f"@{table_name}:[{row},{col}]: unsupported data type {data_type} for save",
|
|
1761
|
-
UnsupportedWarning,
|
|
1762
|
-
stacklevel=1,
|
|
1763
|
-
)
|
|
1764
|
-
return None
|
|
1765
|
-
|
|
1766
|
-
storage = bytearray(12)
|
|
1767
|
-
storage[0] = 5
|
|
1768
|
-
storage[1] = cell_type
|
|
1769
|
-
storage += value
|
|
1770
|
-
|
|
1771
|
-
if cell._storage.rich_id is not None:
|
|
1772
|
-
flags |= 0x10
|
|
1773
|
-
length += 4
|
|
1774
|
-
storage += pack("<i", cell._storage.rich_id)
|
|
1775
|
-
if cell._storage.cell_style_id is not None:
|
|
1776
|
-
flags |= 0x20
|
|
1777
|
-
length += 4
|
|
1778
|
-
storage += pack("<i", cell._storage.cell_style_id)
|
|
1779
|
-
if cell._storage.text_style_id is not None:
|
|
1780
|
-
flags |= 0x40
|
|
1781
|
-
length += 4
|
|
1782
|
-
storage += pack("<i", cell._storage.text_style_id)
|
|
1783
|
-
if cell._storage.formula_id is not None:
|
|
1784
|
-
flags |= 0x200
|
|
1785
|
-
length += 4
|
|
1786
|
-
storage += pack("<i", cell._storage.formula_id)
|
|
1787
|
-
if cell._storage.control_id is not None:
|
|
1788
|
-
flags |= 0x400
|
|
1789
|
-
length += 4
|
|
1790
|
-
storage += pack("<i", cell._storage.control_id)
|
|
1791
|
-
if cell._storage.suggest_id is not None:
|
|
1792
|
-
flags |= 0x1000
|
|
1793
|
-
length += 4
|
|
1794
|
-
storage += pack("<i", cell._storage.suggest_id)
|
|
1795
|
-
if cell._storage.num_format_id is not None:
|
|
1796
|
-
flags |= 0x2000
|
|
1797
|
-
length += 4
|
|
1798
|
-
storage += pack("<i", cell._storage.num_format_id)
|
|
1799
|
-
storage[6] |= 1
|
|
1800
|
-
# storage[6:8] = pack("<h", 1)
|
|
1801
|
-
if cell._storage.currency_format_id is not None:
|
|
1802
|
-
flags |= 0x4000
|
|
1803
|
-
length += 4
|
|
1804
|
-
storage += pack("<i", cell._storage.currency_format_id)
|
|
1805
|
-
storage[6] |= 2
|
|
1806
|
-
if cell._storage.date_format_id is not None:
|
|
1807
|
-
flags |= 0x8000
|
|
1808
|
-
length += 4
|
|
1809
|
-
storage += pack("<i", cell._storage.date_format_id)
|
|
1810
|
-
storage[6] |= 8
|
|
1811
|
-
if cell._storage.duration_format_id is not None:
|
|
1812
|
-
flags |= 0x10000
|
|
1813
|
-
length += 4
|
|
1814
|
-
storage += pack("<i", cell._storage.duration_format_id)
|
|
1815
|
-
storage[6] |= 4
|
|
1816
|
-
if cell._storage.text_format_id is not None:
|
|
1817
|
-
flags |= 0x20000
|
|
1818
|
-
length += 4
|
|
1819
|
-
storage += pack("<i", cell._storage.text_format_id)
|
|
1820
|
-
if cell._storage.bool_format_id is not None:
|
|
1821
|
-
flags |= 0x40000
|
|
1822
|
-
length += 4
|
|
1823
|
-
storage += pack("<i", cell._storage.bool_format_id)
|
|
1824
|
-
storage[6] |= 0x20
|
|
1825
|
-
if cell._storage.string_id is not None:
|
|
1826
|
-
storage[6] |= 0x80
|
|
1827
|
-
|
|
1828
|
-
storage[8:12] = pack("<i", flags)
|
|
1829
|
-
if len(storage) < 32:
|
|
1830
|
-
storage += bytearray(32 - length)
|
|
1831
|
-
|
|
1832
|
-
return storage[0:length]
|
|
1833
|
-
|
|
1834
1685
|
@cache()
|
|
1835
1686
|
def table_formulas(self, table_id: int):
|
|
1836
1687
|
return TableFormulas(self, table_id)
|
|
1837
1688
|
|
|
1838
|
-
@cache(num_args=3)
|
|
1839
|
-
def table_cell_decode(self, table_id: int, row: int, col: int) -> Dict:
|
|
1840
|
-
buffer = self.storage_buffer(table_id, row, col)
|
|
1841
|
-
if buffer is None:
|
|
1842
|
-
return None
|
|
1843
|
-
|
|
1844
|
-
return CellStorage(self, table_id, buffer, row, col)
|
|
1845
|
-
|
|
1846
1689
|
@cache(num_args=2)
|
|
1847
1690
|
def table_rich_text(self, table_id: int, string_key: int) -> Dict:
|
|
1848
1691
|
"""Extract bullets and hyperlinks from a rich text data cell."""
|
|
@@ -1934,41 +1777,41 @@ class _NumbersModel(Cacheable):
|
|
|
1934
1777
|
"hyperlinks": hyperlinks,
|
|
1935
1778
|
}
|
|
1936
1779
|
|
|
1937
|
-
def cell_text_style(self,
|
|
1780
|
+
def cell_text_style(self, cell: Cell) -> object:
|
|
1938
1781
|
"""Return the text style object for the cell or, if none
|
|
1939
1782
|
is defined, the default header, footer or body style.
|
|
1940
1783
|
"""
|
|
1941
|
-
if
|
|
1942
|
-
return self.table_style(
|
|
1784
|
+
if cell._text_style_id is not None:
|
|
1785
|
+
return self.table_style(cell._table_id, cell._text_style_id)
|
|
1943
1786
|
|
|
1944
|
-
table_model = self.objects[
|
|
1945
|
-
if
|
|
1787
|
+
table_model = self.objects[cell._table_id]
|
|
1788
|
+
if cell.row in range(table_model.number_of_header_rows):
|
|
1946
1789
|
return self.objects[table_model.header_row_text_style.identifier]
|
|
1947
|
-
elif
|
|
1790
|
+
elif cell.col in range(table_model.number_of_header_columns):
|
|
1948
1791
|
return self.objects[table_model.header_column_text_style.identifier]
|
|
1949
1792
|
elif table_model.number_of_footer_rows > 0:
|
|
1950
1793
|
start_row_num = table_model.number_of_rows - table_model.number_of_footer_rows
|
|
1951
1794
|
end_row_num = start_row_num + table_model.number_of_footer_rows
|
|
1952
|
-
if
|
|
1795
|
+
if cell.row in range(start_row_num, end_row_num):
|
|
1953
1796
|
return self.objects[table_model.footer_row_text_style.identifier]
|
|
1954
1797
|
return self.objects[table_model.body_text_style.identifier]
|
|
1955
1798
|
|
|
1956
|
-
def cell_alignment(self,
|
|
1957
|
-
style = self.cell_text_style(
|
|
1799
|
+
def cell_alignment(self, cell: Cell) -> Alignment:
|
|
1800
|
+
style = self.cell_text_style(cell)
|
|
1958
1801
|
horizontal = HorizontalJustification(self.para_property(style, "alignment"))
|
|
1959
1802
|
|
|
1960
|
-
if
|
|
1803
|
+
if cell._cell_style_id is None:
|
|
1961
1804
|
vertical = VerticalJustification.TOP
|
|
1962
1805
|
else:
|
|
1963
|
-
style = self.table_style(
|
|
1806
|
+
style = self.table_style(cell._table_id, cell._cell_style_id)
|
|
1964
1807
|
vertical = VerticalJustification(self.cell_property(style, "vertical_alignment"))
|
|
1965
1808
|
return Alignment(horizontal, vertical)
|
|
1966
1809
|
|
|
1967
|
-
def cell_bg_color(self,
|
|
1968
|
-
if
|
|
1810
|
+
def cell_bg_color(self, cell: Cell) -> Union[Tuple, List[Tuple]]:
|
|
1811
|
+
if cell._cell_style_id is None:
|
|
1969
1812
|
return None
|
|
1970
1813
|
|
|
1971
|
-
style = self.table_style(
|
|
1814
|
+
style = self.table_style(cell._table_id, cell._cell_style_id)
|
|
1972
1815
|
cell_properties = style.cell_properties.cell_fill
|
|
1973
1816
|
|
|
1974
1817
|
if cell_properties.HasField("color"):
|
|
@@ -2006,67 +1849,67 @@ class _NumbersModel(Cacheable):
|
|
|
2006
1849
|
else:
|
|
2007
1850
|
return getattr(style.cell_properties, field)
|
|
2008
1851
|
|
|
2009
|
-
def cell_is_bold(self, obj: object) -> bool:
|
|
2010
|
-
style = self.cell_text_style(obj) if isinstance(obj,
|
|
1852
|
+
def cell_is_bold(self, obj: Union[Cell, object]) -> bool:
|
|
1853
|
+
style = self.cell_text_style(obj) if isinstance(obj, Cell) else obj
|
|
2011
1854
|
return self.char_property(style, "bold")
|
|
2012
1855
|
|
|
2013
|
-
def cell_is_italic(self, obj: object) -> bool:
|
|
2014
|
-
style = self.cell_text_style(obj) if isinstance(obj,
|
|
1856
|
+
def cell_is_italic(self, obj: Union[Cell, object]) -> bool:
|
|
1857
|
+
style = self.cell_text_style(obj) if isinstance(obj, Cell) else obj
|
|
2015
1858
|
return self.char_property(style, "italic")
|
|
2016
1859
|
|
|
2017
|
-
def cell_is_underline(self, obj: object) -> bool:
|
|
2018
|
-
style = self.cell_text_style(obj) if isinstance(obj,
|
|
1860
|
+
def cell_is_underline(self, obj: Union[Cell, object]) -> bool:
|
|
1861
|
+
style = self.cell_text_style(obj) if isinstance(obj, Cell) else obj
|
|
2019
1862
|
underline = self.char_property(style, "underline")
|
|
2020
1863
|
return underline != CharacterStyle.UnderlineType.kNoUnderline
|
|
2021
1864
|
|
|
2022
|
-
def cell_is_strikethrough(self, obj: object) -> bool:
|
|
2023
|
-
style = self.cell_text_style(obj) if isinstance(obj,
|
|
1865
|
+
def cell_is_strikethrough(self, obj: Union[Cell, object]) -> bool:
|
|
1866
|
+
style = self.cell_text_style(obj) if isinstance(obj, Cell) else obj
|
|
2024
1867
|
strikethru = self.char_property(style, "strikethru")
|
|
2025
1868
|
return strikethru != CharacterStyle.StrikethruType.kNoStrikethru
|
|
2026
1869
|
|
|
2027
|
-
def cell_style_name(self, obj: object) -> bool:
|
|
2028
|
-
style = self.cell_text_style(obj) if isinstance(obj,
|
|
1870
|
+
def cell_style_name(self, obj: Union[Cell, object]) -> bool:
|
|
1871
|
+
style = self.cell_text_style(obj) if isinstance(obj, Cell) else obj
|
|
2029
1872
|
return style.super.name
|
|
2030
1873
|
|
|
2031
|
-
def cell_font_color(self, obj: object) -> Tuple:
|
|
2032
|
-
style = self.cell_text_style(obj) if isinstance(obj,
|
|
1874
|
+
def cell_font_color(self, obj: Union[Cell, object]) -> Tuple:
|
|
1875
|
+
style = self.cell_text_style(obj) if isinstance(obj, Cell) else obj
|
|
2033
1876
|
return rgb(self.char_property(style, "font_color"))
|
|
2034
1877
|
|
|
2035
|
-
def cell_font_size(self, obj: object) -> float:
|
|
2036
|
-
style = self.cell_text_style(obj) if isinstance(obj,
|
|
1878
|
+
def cell_font_size(self, obj: Union[Cell, object]) -> float:
|
|
1879
|
+
style = self.cell_text_style(obj) if isinstance(obj, Cell) else obj
|
|
2037
1880
|
return self.char_property(style, "font_size")
|
|
2038
1881
|
|
|
2039
|
-
def cell_font_name(self, obj: object) -> str:
|
|
2040
|
-
style = self.cell_text_style(obj) if isinstance(obj,
|
|
1882
|
+
def cell_font_name(self, obj: Union[Cell, object]) -> str:
|
|
1883
|
+
style = self.cell_text_style(obj) if isinstance(obj, Cell) else obj
|
|
2041
1884
|
font_name = self.char_property(style, "font_name")
|
|
2042
1885
|
return FONT_NAME_TO_FAMILY[font_name]
|
|
2043
1886
|
|
|
2044
|
-
def cell_first_indent(self, obj: object) -> float:
|
|
2045
|
-
style = self.cell_text_style(obj) if isinstance(obj,
|
|
1887
|
+
def cell_first_indent(self, obj: Union[Cell, object]) -> float:
|
|
1888
|
+
style = self.cell_text_style(obj) if isinstance(obj, Cell) else obj
|
|
2046
1889
|
return self.para_property(style, "first_line_indent")
|
|
2047
1890
|
|
|
2048
|
-
def cell_left_indent(self, obj: object) -> float:
|
|
2049
|
-
style = self.cell_text_style(obj) if isinstance(obj,
|
|
1891
|
+
def cell_left_indent(self, obj: Union[Cell, object]) -> float:
|
|
1892
|
+
style = self.cell_text_style(obj) if isinstance(obj, Cell) else obj
|
|
2050
1893
|
return self.para_property(style, "left_indent")
|
|
2051
1894
|
|
|
2052
|
-
def cell_right_indent(self, obj: object) -> float:
|
|
2053
|
-
style = self.cell_text_style(obj) if isinstance(obj,
|
|
1895
|
+
def cell_right_indent(self, obj: Union[Cell, object]) -> float:
|
|
1896
|
+
style = self.cell_text_style(obj) if isinstance(obj, Cell) else obj
|
|
2054
1897
|
return self.para_property(style, "right_indent")
|
|
2055
1898
|
|
|
2056
|
-
def cell_text_inset(self,
|
|
2057
|
-
if
|
|
1899
|
+
def cell_text_inset(self, cell: Cell) -> float:
|
|
1900
|
+
if cell._cell_style_id is None:
|
|
2058
1901
|
return DEFAULT_TEXT_INSET
|
|
2059
1902
|
else:
|
|
2060
|
-
style = self.table_style(
|
|
1903
|
+
style = self.table_style(cell._table_id, cell._cell_style_id)
|
|
2061
1904
|
padding = self.cell_property(style, "padding")
|
|
2062
1905
|
# Padding is always identical (only one UI setting)
|
|
2063
1906
|
return padding.left
|
|
2064
1907
|
|
|
2065
|
-
def cell_text_wrap(self,
|
|
2066
|
-
if
|
|
1908
|
+
def cell_text_wrap(self, cell: Cell) -> float:
|
|
1909
|
+
if cell._cell_style_id is None:
|
|
2067
1910
|
return DEFAULT_TEXT_WRAP
|
|
2068
1911
|
else:
|
|
2069
|
-
style = self.table_style(
|
|
1912
|
+
style = self.table_style(cell._table_id, cell._cell_style_id)
|
|
2070
1913
|
return self.cell_property(style, "text_wrap")
|
|
2071
1914
|
|
|
2072
1915
|
def stroke_type(self, stroke_run: object) -> str:
|
|
@@ -2418,23 +2261,6 @@ def clear_field_container(obj):
|
|
|
2418
2261
|
_ = obj.pop()
|
|
2419
2262
|
|
|
2420
2263
|
|
|
2421
|
-
def pack_decimal128(value: float) -> bytearray:
|
|
2422
|
-
buffer = bytearray(16)
|
|
2423
|
-
exp = math.floor(math.log10(math.e) * math.log(abs(value))) if value != 0.0 else 0
|
|
2424
|
-
exp += 0x1820 - 16
|
|
2425
|
-
mantissa = abs(int(value / math.pow(10, exp - 0x1820)))
|
|
2426
|
-
buffer[15] |= exp >> 7
|
|
2427
|
-
buffer[14] |= (exp & 0x7F) << 1
|
|
2428
|
-
i = 0
|
|
2429
|
-
while mantissa >= 1:
|
|
2430
|
-
buffer[i] = mantissa & 0xFF
|
|
2431
|
-
i += 1
|
|
2432
|
-
mantissa = int(mantissa / 256)
|
|
2433
|
-
if value < 0:
|
|
2434
|
-
buffer[15] |= 0x80
|
|
2435
|
-
return buffer
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
2264
|
def field_references(obj: object) -> dict:
|
|
2439
2265
|
"""Return a dict of all fields in an object that are references to other objects."""
|
|
2440
2266
|
return {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: numbers-parser
|
|
3
|
-
Version: 4.10.
|
|
3
|
+
Version: 4.10.3
|
|
4
4
|
Summary: Read and write Apple Numbers spreadsheets
|
|
5
5
|
Home-page: https://github.com/masaccio/numbers-parser
|
|
6
6
|
License: MIT
|
|
@@ -19,7 +19,7 @@ Requires-Dist: compact-json (>=1.1.3,<2.0.0)
|
|
|
19
19
|
Requires-Dist: enum-tools (>=0.11.0,<0.12.0)
|
|
20
20
|
Requires-Dist: importlib-resources (>=6.1.1,<7.0.0)
|
|
21
21
|
Requires-Dist: pendulum (>=3.0,<4.0)
|
|
22
|
-
Requires-Dist: protobuf
|
|
22
|
+
Requires-Dist: protobuf
|
|
23
23
|
Requires-Dist: python-snappy (>=0.6.1,<0.7.0)
|
|
24
24
|
Requires-Dist: regex (>=2022.9.13,<2023.0.0)
|
|
25
25
|
Requires-Dist: roman (>=3.3,<4.0)
|
|
@@ -37,7 +37,7 @@ Description-Content-Type: text/markdown
|
|
|
37
37
|
generated by Numbers version 10.3, and up with the latest tested version being 13.2
|
|
38
38
|
(current as of September 2023).
|
|
39
39
|
|
|
40
|
-
It supports and is tested against Python versions from 3.
|
|
40
|
+
It supports and is tested against Python versions from 3.9 onwards. It is not compatible
|
|
41
41
|
with earlier versions of Python.
|
|
42
42
|
|
|
43
43
|
## Installation
|
|
@@ -376,24 +376,27 @@ cannot return lower-case versions of AM/PM.
|
|
|
376
376
|
|
|
377
377
|
## Limitations
|
|
378
378
|
|
|
379
|
-
Current known limitations of `numbers-parser` are:
|
|
379
|
+
Current known limitations of `numbers-parser` which may be implemented in the future are:
|
|
380
380
|
|
|
381
|
-
- Formulas cannot be written to a document
|
|
382
381
|
- Table styles that allow new tables to adopt a style across the whole
|
|
383
|
-
table are not
|
|
382
|
+
table are not suppported
|
|
384
383
|
- Creating cells of type `BulletedTextCell` is not supported
|
|
385
384
|
- New tables are inserted with a fixed offset below the last table in a
|
|
386
385
|
worksheet which does not take into account title or caption size
|
|
386
|
+
- Formulas cannot be written to a document
|
|
387
|
+
- Pivot tables are unsupported and saving a document with a pivot table issues
|
|
388
|
+
a UnsupportedWarning.
|
|
389
|
+
|
|
390
|
+
The following limitations are expected to always remain:
|
|
391
|
+
|
|
387
392
|
- New sheets insert tables with formats copied from the first table in
|
|
388
393
|
the previous sheet rather than default table formats
|
|
389
394
|
- Due to a limitation in Python’s
|
|
390
395
|
[ZipFile](https://docs.python.org/3/library/zipfile.html), Python
|
|
391
|
-
versions older than 3.11 do not support image filenames with UTF-8
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
[
|
|
395
|
-
returns `None` for such files and issues a `RuntimeWarning`.
|
|
396
|
-
- Pivot tables are unsupported, but re-saving a document is believed to work. Saving a document with a pivot table issues a UnsupportedWarning.
|
|
396
|
+
versions older than 3.11 do not support image filenames with UTF-8 characters
|
|
397
|
+
[Cell.add_style.bg_image()](https://masaccio.github.io/numbers-parser/api/sheet.html#numbers_parser.Style) returns
|
|
398
|
+
`None` for such files and issues a `RuntimeWarning`
|
|
399
|
+
(see [issue 69](https://github.com/masaccio/numbers-parser/ssues/69) for details).
|
|
397
400
|
|
|
398
401
|
## License
|
|
399
402
|
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
numbers_parser/__init__.py,sha256=
|
|
1
|
+
numbers_parser/__init__.py,sha256=w0ZhMcFpVol1VwjyYHjvqBmshvsMDfLIyFX-GmpOpMQ,1410
|
|
2
2
|
numbers_parser/_cat_numbers.py,sha256=-HboBJT11Vjcr8sjLZl7Z6qAapnPEc_kFYq6PTqON20,4619
|
|
3
3
|
numbers_parser/_unpack_numbers.py,sha256=U5SL16uNNe0BvYtUtIYJT-t3RN36Q8U-8UTr7lnOylw,7040
|
|
4
4
|
numbers_parser/bullets.py,sha256=OnVVMPjhTDrC-ncw52Gb00UEXNmn2Rvd3xi7lfqW3hk,2616
|
|
5
|
-
numbers_parser/cell.py,sha256=
|
|
6
|
-
numbers_parser/
|
|
7
|
-
numbers_parser/
|
|
8
|
-
numbers_parser/containers.py,sha256=FcNXbFM_VOe1XCT_WAuFOJwE47h6961_zv2_6OIsVoo,4749
|
|
5
|
+
numbers_parser/cell.py,sha256=q8gDmrncUpZTNx-XGfTRDToDVFudSkOMhDLMOEJU5ss,75511
|
|
6
|
+
numbers_parser/constants.py,sha256=gROxeu8wDj-a96rWy2bR_c8CoF0RagqWI3V4SE6SN4M,9908
|
|
7
|
+
numbers_parser/containers.py,sha256=EnHcsmHIFLsRiZsjhqMcJL20k6sHMsd5neVDFNUDuwc,4751
|
|
9
8
|
numbers_parser/currencies.py,sha256=8k4a3WKmDoHeurkDICymHX13N7ManHSTaka_JNXCZYA,3767
|
|
10
9
|
numbers_parser/data/empty.numbers,sha256=8JOp035V-p2ff9_Wao7mLcYvb6_if6O2cus_esjVA9k,90316
|
|
11
|
-
numbers_parser/document.py,sha256=
|
|
10
|
+
numbers_parser/document.py,sha256=34BegFJnEONgaAJbKG5zlaiJ4RnaJmQw3Zbw54SM8YM,56813
|
|
12
11
|
numbers_parser/exceptions.py,sha256=G8dASUQZI8ksHYRVfdGWJzgsJD5CBpcZvmDJUZTqT-c,670
|
|
13
12
|
numbers_parser/experimental.py,sha256=WARjTa-2ePb8Ga8Q6oDP6EJCs12ofLRF2YpwzUu66ZI,374
|
|
14
13
|
numbers_parser/formula.py,sha256=JRsG0L21wS70oJ-FB46Amyoy-sKizWb-iUhSXUcVJ-U,10572
|
|
@@ -50,11 +49,11 @@ numbers_parser/generated/functionmap.py,sha256=VdZo0ERMYONcrnJFwABcSCHb8pjA4wY2o
|
|
|
50
49
|
numbers_parser/iwafile.py,sha256=xHDBZWd5EEaPHonlAAl4OYmmaXg_Nw4H0OmItt91URg,11861
|
|
51
50
|
numbers_parser/iwork.py,sha256=U0Irxt-3v3vFP_feCuYMDxWUgqeq_YHMCfw1tsDpUcM,8947
|
|
52
51
|
numbers_parser/mapping.py,sha256=in8W3S8DmTcPefFaxnATLw0FQ4YnFsnAE-cl5dljzJE,32215
|
|
53
|
-
numbers_parser/model.py,sha256=
|
|
52
|
+
numbers_parser/model.py,sha256=0DKFurS9DSLwdIvij4Zz5CjDDvlYC4ZlN2DnoxW4nIU,97058
|
|
54
53
|
numbers_parser/numbers_cache.py,sha256=1ghEBghQAYFpPiEeOtb74i016mXc039v1pOubbqvaLs,1141
|
|
55
54
|
numbers_parser/numbers_uuid.py,sha256=-LeAj_ULC0va57pEmyegGY0xXqkNNjyuLukCaiQJhOk,2642
|
|
56
|
-
numbers_parser-4.10.
|
|
57
|
-
numbers_parser-4.10.
|
|
58
|
-
numbers_parser-4.10.
|
|
59
|
-
numbers_parser-4.10.
|
|
60
|
-
numbers_parser-4.10.
|
|
55
|
+
numbers_parser-4.10.3.dist-info/LICENSE.rst,sha256=8vTa1-5KSdHrTpU9rlheO5005EWReEPMpjV7BjSaMc4,1050
|
|
56
|
+
numbers_parser-4.10.3.dist-info/METADATA,sha256=CNp5aHpYLS0CehOLWXXE-vE4hl3w6qVsaEEZagB6RCE,16267
|
|
57
|
+
numbers_parser-4.10.3.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
|
58
|
+
numbers_parser-4.10.3.dist-info/entry_points.txt,sha256=V91uB9vBPxf3eCY1h-0syv21imYCT0MJfMxf87DmwIk,115
|
|
59
|
+
numbers_parser-4.10.3.dist-info/RECORD,,
|