numbers-parser 4.4.6__py3-none-any.whl → 4.4.8__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 +7 -5
- numbers_parser/_cat_numbers.py +1 -2
- numbers_parser/_unpack_numbers.py +14 -16
- numbers_parser/bullets.py +1 -1
- numbers_parser/cell.py +33 -29
- numbers_parser/cell_storage.py +36 -29
- numbers_parser/constants.py +2 -2
- numbers_parser/containers.py +12 -14
- numbers_parser/document.py +54 -45
- numbers_parser/exceptions.py +7 -7
- numbers_parser/file.py +18 -8
- numbers_parser/formula.py +7 -3
- numbers_parser/iwafile.py +23 -27
- numbers_parser/mapping.py +24 -24
- numbers_parser/model.py +112 -131
- numbers_parser/numbers_cache.py +3 -2
- {numbers_parser-4.4.6.dist-info → numbers_parser-4.4.8.dist-info}/METADATA +8 -5
- {numbers_parser-4.4.6.dist-info → numbers_parser-4.4.8.dist-info}/RECORD +21 -21
- {numbers_parser-4.4.6.dist-info → numbers_parser-4.4.8.dist-info}/LICENSE.rst +0 -0
- {numbers_parser-4.4.6.dist-info → numbers_parser-4.4.8.dist-info}/WHEEL +0 -0
- {numbers_parser-4.4.6.dist-info → numbers_parser-4.4.8.dist-info}/entry_points.txt +0 -0
numbers_parser/model.py
CHANGED
|
@@ -1,30 +1,18 @@
|
|
|
1
1
|
import math
|
|
2
2
|
import re
|
|
3
|
-
|
|
4
3
|
from array import array
|
|
5
4
|
from collections import defaultdict
|
|
6
|
-
from numbers_parser.numbers_cache import cache, Cacheable
|
|
7
5
|
from struct import pack
|
|
8
6
|
from typing import Dict, List, Tuple, Union
|
|
9
7
|
from warnings import warn
|
|
10
8
|
|
|
11
|
-
from numbers_parser.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
DEFAULT_COLUMN_WIDTH,
|
|
16
|
-
DEFAULT_DOCUMENT,
|
|
17
|
-
DEFAULT_PRE_BNC_BYTES,
|
|
18
|
-
DEFAULT_ROW_HEIGHT,
|
|
19
|
-
DEFAULT_TABLE_OFFSET,
|
|
20
|
-
DEFAULT_TILE_SIZE,
|
|
21
|
-
DEFAULT_TEXT_INSET,
|
|
22
|
-
DEFAULT_TEXT_WRAP,
|
|
23
|
-
DOCUMENT_ID,
|
|
24
|
-
PACKAGE_ID,
|
|
25
|
-
MAX_TILE_SIZE,
|
|
9
|
+
from numbers_parser.bullets import (
|
|
10
|
+
BULLET_CONVERSION,
|
|
11
|
+
BULLET_PREFIXES,
|
|
12
|
+
BULLET_SUFFIXES,
|
|
26
13
|
)
|
|
27
14
|
from numbers_parser.cell import (
|
|
15
|
+
RGB,
|
|
28
16
|
Alignment,
|
|
29
17
|
BoolCell,
|
|
30
18
|
Border,
|
|
@@ -37,7 +25,6 @@ from numbers_parser.cell import (
|
|
|
37
25
|
MergedCell,
|
|
38
26
|
MergeReference,
|
|
39
27
|
NumberCell,
|
|
40
|
-
RGB,
|
|
41
28
|
RichTextCell,
|
|
42
29
|
Style,
|
|
43
30
|
TextCell,
|
|
@@ -46,31 +33,42 @@ from numbers_parser.cell import (
|
|
|
46
33
|
xl_range,
|
|
47
34
|
xl_rowcol_to_cell,
|
|
48
35
|
)
|
|
36
|
+
from numbers_parser.cell_storage import CellStorage
|
|
37
|
+
from numbers_parser.constants import (
|
|
38
|
+
DEFAULT_COLUMN_WIDTH,
|
|
39
|
+
DEFAULT_DOCUMENT,
|
|
40
|
+
DEFAULT_PRE_BNC_BYTES,
|
|
41
|
+
DEFAULT_ROW_HEIGHT,
|
|
42
|
+
DEFAULT_TABLE_OFFSET,
|
|
43
|
+
DEFAULT_TEXT_INSET,
|
|
44
|
+
DEFAULT_TEXT_WRAP,
|
|
45
|
+
DEFAULT_TILE_SIZE,
|
|
46
|
+
DOCUMENT_ID,
|
|
47
|
+
EPOCH,
|
|
48
|
+
MAX_TILE_SIZE,
|
|
49
|
+
PACKAGE_ID,
|
|
50
|
+
)
|
|
51
|
+
from numbers_parser.containers import ObjectStore
|
|
49
52
|
from numbers_parser.exceptions import UnsupportedError, UnsupportedWarning
|
|
50
53
|
from numbers_parser.formula import TableFormulas
|
|
51
|
-
from numbers_parser.bullets import (
|
|
52
|
-
BULLET_PREFIXES,
|
|
53
|
-
BULLET_CONVERSION,
|
|
54
|
-
BULLET_SUFFIXES,
|
|
55
|
-
)
|
|
56
|
-
from numbers_parser.cell_storage import CellStorage
|
|
57
|
-
from numbers_parser.numbers_uuid import NumbersUUID
|
|
58
|
-
|
|
59
|
-
from numbers_parser.generated.fontmap import FONT_NAME_TO_FAMILY
|
|
60
54
|
from numbers_parser.generated import TNArchives_pb2 as TNArchives
|
|
55
|
+
from numbers_parser.generated import TSCEArchives_pb2 as TSCEArchives
|
|
61
56
|
from numbers_parser.generated import TSDArchives_pb2 as TSDArchives
|
|
62
|
-
from numbers_parser.generated import TSPMessages_pb2 as TSPMessages
|
|
63
57
|
from numbers_parser.generated import TSPArchiveMessages_pb2 as TSPArchiveMessages
|
|
58
|
+
from numbers_parser.generated import TSPMessages_pb2 as TSPMessages
|
|
59
|
+
from numbers_parser.generated import TSSArchives_pb2 as TSSArchives
|
|
64
60
|
from numbers_parser.generated import TSTArchives_pb2 as TSTArchives
|
|
65
|
-
from numbers_parser.generated import TSCEArchives_pb2 as TSCEArchives
|
|
66
61
|
from numbers_parser.generated import TSWPArchives_pb2 as TSWPArchives
|
|
67
|
-
from numbers_parser.generated import
|
|
68
|
-
from numbers_parser.generated.TSWPArchives_pb2 import (
|
|
69
|
-
CharacterStylePropertiesArchive as CharacterStyle,
|
|
70
|
-
)
|
|
62
|
+
from numbers_parser.generated.fontmap import FONT_NAME_TO_FAMILY
|
|
71
63
|
from numbers_parser.generated.TSDArchives_pb2 import (
|
|
72
64
|
StrokePatternArchive as StrokePattern,
|
|
73
65
|
)
|
|
66
|
+
from numbers_parser.generated.TSWPArchives_pb2 import (
|
|
67
|
+
CharacterStylePropertiesArchive as CharacterStyle,
|
|
68
|
+
)
|
|
69
|
+
from numbers_parser.iwafile import find_extension
|
|
70
|
+
from numbers_parser.numbers_cache import Cacheable, cache
|
|
71
|
+
from numbers_parser.numbers_uuid import NumbersUUID
|
|
74
72
|
|
|
75
73
|
|
|
76
74
|
def create_font_name_map(font_map: dict) -> dict:
|
|
@@ -110,21 +108,19 @@ class MergeCells:
|
|
|
110
108
|
return self._references[row_col].rect
|
|
111
109
|
|
|
112
110
|
def merge_cell_names(self):
|
|
113
|
-
|
|
111
|
+
return [
|
|
114
112
|
xl_range(*v.rect) for k, v in self._references.items() if self.is_merge_reference(k)
|
|
115
113
|
]
|
|
116
|
-
return ranges
|
|
117
114
|
|
|
118
115
|
def merge_cells(self):
|
|
119
|
-
|
|
120
|
-
return ranges
|
|
116
|
+
return [k for k, v in self._references.items() if self.is_merge_anchor(k)]
|
|
121
117
|
|
|
122
118
|
def __len__(self):
|
|
123
119
|
return len(self._references.keys())
|
|
124
120
|
|
|
125
121
|
|
|
126
122
|
class DataLists(Cacheable):
|
|
127
|
-
"""Model for TST.DataList with caching and key generation for new values"""
|
|
123
|
+
"""Model for TST.DataList with caching and key generation for new values."""
|
|
128
124
|
|
|
129
125
|
def __init__(self, model: object, datalist_name: str, value_attr: str = None):
|
|
130
126
|
self._model = model
|
|
@@ -134,7 +130,7 @@ class DataLists(Cacheable):
|
|
|
134
130
|
|
|
135
131
|
@cache()
|
|
136
132
|
def add_table(self, table_id: int):
|
|
137
|
-
"""Cache a new datalist for a table if not already seen"""
|
|
133
|
+
"""Cache a new datalist for a table if not already seen."""
|
|
138
134
|
base_data_store = self._model.objects[table_id].base_data_store
|
|
139
135
|
datalist_id = getattr(base_data_store, self._datalist_name).identifier
|
|
140
136
|
datalist = self._model.objects[datalist_id]
|
|
@@ -160,12 +156,12 @@ class DataLists(Cacheable):
|
|
|
160
156
|
return self._datalists[table_id]["id"]
|
|
161
157
|
|
|
162
158
|
def lookup_value(self, table_id: int, key: int):
|
|
163
|
-
"""Return the an entry in a table's datalist matching a key"""
|
|
159
|
+
"""Return the an entry in a table's datalist matching a key."""
|
|
164
160
|
self.add_table(table_id)
|
|
165
161
|
return self._datalists[table_id]["by_key"][key]
|
|
166
162
|
|
|
167
163
|
def init(self, table_id: int):
|
|
168
|
-
"""Remove all entries from a datalist"""
|
|
164
|
+
"""Remove all entries from a datalist."""
|
|
169
165
|
self.add_table(table_id)
|
|
170
166
|
self._datalists[table_id]["by_key"] = {}
|
|
171
167
|
self._datalists[table_id]["by_value"] = {}
|
|
@@ -176,7 +172,8 @@ class DataLists(Cacheable):
|
|
|
176
172
|
def lookup_key(self, table_id: int, value) -> int:
|
|
177
173
|
"""Return the key associated with a value for a particular table entry.
|
|
178
174
|
If the value is not in the datalist, allocate a new entry with the
|
|
179
|
-
next available key
|
|
175
|
+
next available key.
|
|
176
|
+
"""
|
|
180
177
|
self.add_table(table_id)
|
|
181
178
|
value_key = value.identifier if isinstance(value, TSPMessages.Reference) else value
|
|
182
179
|
if value_key not in self._datalists[table_id]["by_value"]:
|
|
@@ -198,8 +195,7 @@ class DataLists(Cacheable):
|
|
|
198
195
|
|
|
199
196
|
|
|
200
197
|
class _NumbersModel(Cacheable):
|
|
201
|
-
"""
|
|
202
|
-
Loads all objects from Numbers document and provides decoding
|
|
198
|
+
"""Loads all objects from Numbers document and provides decoding
|
|
203
199
|
methods for other classes in the module to abstract away the
|
|
204
200
|
internal structures of Numbers document data structures.
|
|
205
201
|
|
|
@@ -246,7 +242,7 @@ class _NumbersModel(Cacheable):
|
|
|
246
242
|
|
|
247
243
|
# Don't cache: new tables can be added at runtime
|
|
248
244
|
def table_ids(self, sheet_id: int) -> list:
|
|
249
|
-
"""Return a list of table IDs for a given sheet ID"""
|
|
245
|
+
"""Return a list of table IDs for a given sheet ID."""
|
|
250
246
|
table_info_ids = self.find_refs("TableInfoArchive")
|
|
251
247
|
return [
|
|
252
248
|
self.objects[t_id].tableModel.identifier
|
|
@@ -256,7 +252,7 @@ class _NumbersModel(Cacheable):
|
|
|
256
252
|
|
|
257
253
|
# Don't cache: new tables can be added at runtime
|
|
258
254
|
def table_info_id(self, table_id: int) -> int:
|
|
259
|
-
"""Return the TableInfoArchive ID for a given table ID"""
|
|
255
|
+
"""Return the TableInfoArchive ID for a given table ID."""
|
|
260
256
|
ids = [
|
|
261
257
|
x
|
|
262
258
|
for x in self.objects.find_refs("TableInfoArchive")
|
|
@@ -337,36 +333,36 @@ class _NumbersModel(Cacheable):
|
|
|
337
333
|
def custom_format_map(self):
|
|
338
334
|
custom_format_list_id = self.objects[DOCUMENT_ID].super.custom_format_list.identifier
|
|
339
335
|
custom_format_list = self.objects[custom_format_list_id]
|
|
340
|
-
|
|
336
|
+
return {
|
|
341
337
|
NumbersUUID(u).hex: custom_format_list.custom_formats[i]
|
|
342
338
|
for i, u in enumerate(custom_format_list.uuids)
|
|
343
339
|
}
|
|
344
|
-
return custom_format_map
|
|
345
340
|
|
|
346
341
|
@cache(num_args=2)
|
|
347
342
|
def table_format(self, table_id: int, key: int) -> str:
|
|
348
|
-
"""Return the format associated with a format ID for a particular table"""
|
|
343
|
+
"""Return the format associated with a format ID for a particular table."""
|
|
349
344
|
return self._table_formats.lookup_value(table_id, key).format
|
|
350
345
|
|
|
351
346
|
@cache(num_args=2)
|
|
352
347
|
def table_style(self, table_id: int, key: int) -> str:
|
|
353
|
-
"""Return the style associated with a style ID for a particular table"""
|
|
348
|
+
"""Return the style associated with a style ID for a particular table."""
|
|
354
349
|
style_entry = self._table_styles.lookup_value(table_id, key)
|
|
355
350
|
return self.objects[style_entry.reference.identifier]
|
|
356
351
|
|
|
357
352
|
@cache(num_args=2)
|
|
358
353
|
def table_string(self, table_id: int, key: int) -> str:
|
|
359
|
-
"""Return the string associated with a string ID for a particular table"""
|
|
354
|
+
"""Return the string associated with a string ID for a particular table."""
|
|
360
355
|
return self._table_strings.lookup_value(table_id, key).string
|
|
361
356
|
|
|
362
357
|
def init_table_strings(self, table_id: int):
|
|
363
|
-
"""Cache table strings reference and delete all existing keys/values"""
|
|
358
|
+
"""Cache table strings reference and delete all existing keys/values."""
|
|
364
359
|
self._table_strings.init(table_id)
|
|
365
360
|
|
|
366
361
|
def table_string_key(self, table_id: int, value: str) -> int:
|
|
367
362
|
"""Return the key associated with a string for a particular table. If
|
|
368
363
|
the string is not in the strings table, allocate a new entry with the
|
|
369
|
-
next available key
|
|
364
|
+
next available key.
|
|
365
|
+
"""
|
|
370
366
|
return self._table_strings.lookup_key(table_id, value)
|
|
371
367
|
|
|
372
368
|
@cache(num_args=0)
|
|
@@ -398,7 +394,7 @@ class _NumbersModel(Cacheable):
|
|
|
398
394
|
|
|
399
395
|
@cache()
|
|
400
396
|
def table_base_id(self, table_id: int) -> int:
|
|
401
|
-
""" "Finds the UUID of a table"""
|
|
397
|
+
""" "Finds the UUID of a table."""
|
|
402
398
|
# Look for a TSCE.FormulaOwnerDependenciesArchive objects with the following at the
|
|
403
399
|
# root level of the protobuf:
|
|
404
400
|
#
|
|
@@ -442,7 +438,7 @@ class _NumbersModel(Cacheable):
|
|
|
442
438
|
|
|
443
439
|
@cache(num_args=0)
|
|
444
440
|
def calc_engine_id(self):
|
|
445
|
-
"""Return the CalculationEngine ID for the current document"""
|
|
441
|
+
"""Return the CalculationEngine ID for the current document."""
|
|
446
442
|
ce_id = self.find_refs("CalculationEngineArchive")
|
|
447
443
|
if len(ce_id) == 0:
|
|
448
444
|
return 0
|
|
@@ -451,7 +447,7 @@ class _NumbersModel(Cacheable):
|
|
|
451
447
|
|
|
452
448
|
@cache(num_args=0)
|
|
453
449
|
def calc_engine(self):
|
|
454
|
-
"""Return the CalculationEngine object for the current document"""
|
|
450
|
+
"""Return the CalculationEngine object for the current document."""
|
|
455
451
|
ce_id = self.calc_engine_id()
|
|
456
452
|
if ce_id == 0:
|
|
457
453
|
return None
|
|
@@ -488,8 +484,8 @@ class _NumbersModel(Cacheable):
|
|
|
488
484
|
if base_data_store.merge_region_map.identifier == 0:
|
|
489
485
|
return
|
|
490
486
|
|
|
491
|
-
|
|
492
|
-
for cell_range in
|
|
487
|
+
cell_ranges = self.objects[base_data_store.merge_region_map.identifier]
|
|
488
|
+
for cell_range in cell_ranges.cell_range:
|
|
493
489
|
(col_start, row_start) = (
|
|
494
490
|
cell_range.origin.packedData >> 16,
|
|
495
491
|
cell_range.origin.packedData & 0xFFFF,
|
|
@@ -716,7 +712,7 @@ class _NumbersModel(Cacheable):
|
|
|
716
712
|
|
|
717
713
|
@cache()
|
|
718
714
|
def metadata_component(self, reference: Union[str, int] = None) -> int:
|
|
719
|
-
"""Return the ID of an object in the document metadata given it's name or ID"""
|
|
715
|
+
"""Return the ID of an object in the document metadata given it's name or ID."""
|
|
720
716
|
component_map = {c.identifier: c for c in self.objects[PACKAGE_ID].components}
|
|
721
717
|
if isinstance(reference, str):
|
|
722
718
|
component_ids = [
|
|
@@ -727,7 +723,7 @@ class _NumbersModel(Cacheable):
|
|
|
727
723
|
return component_map[component_ids[0]]
|
|
728
724
|
|
|
729
725
|
def add_component_metadata(self, object_id: int, parent: str, locator: str):
|
|
730
|
-
"""Add a new ComponentInfo record to the parent object in the document metadata"""
|
|
726
|
+
"""Add a new ComponentInfo record to the parent object in the document metadata."""
|
|
731
727
|
locator = locator.format(object_id)
|
|
732
728
|
preferred_locator = re.sub(r"\-\d+.*", "", locator)
|
|
733
729
|
component_info = TSPArchiveMessages.ComponentInfo(
|
|
@@ -749,7 +745,7 @@ class _NumbersModel(Cacheable):
|
|
|
749
745
|
parent_id: int = None,
|
|
750
746
|
is_weak: bool = False,
|
|
751
747
|
):
|
|
752
|
-
"""Add an external reference to an object in a metadata component"""
|
|
748
|
+
"""Add an external reference to an object in a metadata component."""
|
|
753
749
|
component = self.metadata_component(location or parent_id)
|
|
754
750
|
if parent_id is not None:
|
|
755
751
|
params = {"object_identifier": object_id, "component_identifier": parent_id}
|
|
@@ -830,7 +826,7 @@ class _NumbersModel(Cacheable):
|
|
|
830
826
|
return table_strings_id, table_strings
|
|
831
827
|
|
|
832
828
|
def table_height(self, table_id: int) -> int:
|
|
833
|
-
"""Return the height of a table in points"""
|
|
829
|
+
"""Return the height of a table in points."""
|
|
834
830
|
table_model = self.objects[table_id]
|
|
835
831
|
bds = self.objects[table_id].base_data_store
|
|
836
832
|
buckets = self.objects[bds.rowHeaders.buckets[0].identifier].headers
|
|
@@ -866,7 +862,7 @@ class _NumbersModel(Cacheable):
|
|
|
866
862
|
return round(table_model.default_row_height)
|
|
867
863
|
|
|
868
864
|
def table_width(self, table_id: int) -> int:
|
|
869
|
-
"""Return the width of a table in points"""
|
|
865
|
+
"""Return the width of a table in points."""
|
|
870
866
|
table_model = self.objects[table_id]
|
|
871
867
|
bds = self.objects[table_id].base_data_store
|
|
872
868
|
buckets = self.objects[bds.columnHeaders.identifier].headers
|
|
@@ -902,14 +898,14 @@ class _NumbersModel(Cacheable):
|
|
|
902
898
|
return round(table_model.default_column_width)
|
|
903
899
|
|
|
904
900
|
def num_header_rows(self, table_id: int, num_headers: int = None) -> int:
|
|
905
|
-
"""Return/set the number of header rows"""
|
|
901
|
+
"""Return/set the number of header rows."""
|
|
906
902
|
table_model = self.objects[table_id]
|
|
907
903
|
if num_headers is not None:
|
|
908
904
|
table_model.number_of_header_rows = num_headers
|
|
909
905
|
return table_model.number_of_header_rows
|
|
910
906
|
|
|
911
907
|
def num_header_cols(self, table_id: int, num_headers: int = None) -> int:
|
|
912
|
-
"""Return/set the number of header columns"""
|
|
908
|
+
"""Return/set the number of header columns."""
|
|
913
909
|
table_model = self.objects[table_id]
|
|
914
910
|
if num_headers is not None:
|
|
915
911
|
table_model.number_of_header_columns = num_headers
|
|
@@ -923,7 +919,7 @@ class _NumbersModel(Cacheable):
|
|
|
923
919
|
)
|
|
924
920
|
|
|
925
921
|
def last_table_offset(self, sheet_id):
|
|
926
|
-
"""Y offset of the last table in a sheet"""
|
|
922
|
+
"""Y offset of the last table in a sheet."""
|
|
927
923
|
table_id = self.table_ids(sheet_id)[-1]
|
|
928
924
|
y_offset = [
|
|
929
925
|
self.objects[self.table_info_id(x)].super.geometry.position.y
|
|
@@ -934,16 +930,10 @@ class _NumbersModel(Cacheable):
|
|
|
934
930
|
return self.table_height(table_id) + y_offset
|
|
935
931
|
|
|
936
932
|
def create_drawable(self, sheet_id: int, x: float, y: float) -> object:
|
|
937
|
-
"""Create a DrawableArchive for a new table in a sheet"""
|
|
938
|
-
if x is not None
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
table_x = 0.0
|
|
942
|
-
if y is not None:
|
|
943
|
-
table_y = y
|
|
944
|
-
else:
|
|
945
|
-
table_y = self.last_table_offset(sheet_id) + DEFAULT_TABLE_OFFSET
|
|
946
|
-
drawable = TSDArchives.DrawableArchive(
|
|
933
|
+
"""Create a DrawableArchive for a new table in a sheet."""
|
|
934
|
+
table_x = x if x is not None else 0.0
|
|
935
|
+
table_y = y if y is not None else self.last_table_offset(sheet_id) + DEFAULT_TABLE_OFFSET
|
|
936
|
+
return TSDArchives.DrawableArchive(
|
|
947
937
|
parent=TSPMessages.Reference(identifier=sheet_id),
|
|
948
938
|
geometry=TSDArchives.GeometryArchive(
|
|
949
939
|
angle=0.0,
|
|
@@ -952,7 +942,6 @@ class _NumbersModel(Cacheable):
|
|
|
952
942
|
size=TSPMessages.Size(height=231.0, width=494.0),
|
|
953
943
|
),
|
|
954
944
|
)
|
|
955
|
-
return drawable
|
|
956
945
|
|
|
957
946
|
def add_table(
|
|
958
947
|
self,
|
|
@@ -1050,8 +1039,8 @@ class _NumbersModel(Cacheable):
|
|
|
1050
1039
|
)
|
|
1051
1040
|
|
|
1052
1041
|
data = [
|
|
1053
|
-
[EmptyCell(row_num, col_num) for col_num in range(
|
|
1054
|
-
for row_num in range(
|
|
1042
|
+
[EmptyCell(row_num, col_num) for col_num in range(num_cols)]
|
|
1043
|
+
for row_num in range(num_rows)
|
|
1055
1044
|
]
|
|
1056
1045
|
|
|
1057
1046
|
row_headers_id, _ = self.objects.create_object_from_dict(
|
|
@@ -1100,7 +1089,8 @@ class _NumbersModel(Cacheable):
|
|
|
1100
1089
|
number_of_header_columns: int,
|
|
1101
1090
|
):
|
|
1102
1091
|
"""Create a FormulaOwnerDependenciesArchive that references a TableInfoArchive
|
|
1103
|
-
so that cross-references to cells in this table will work.
|
|
1092
|
+
so that cross-references to cells in this table will work.
|
|
1093
|
+
"""
|
|
1104
1094
|
formula_owner_uuid = NumbersUUID()
|
|
1105
1095
|
calc_engine = self.calc_engine()
|
|
1106
1096
|
owner_id_map = calc_engine.dependency_tracker.owner_id_map.map_entry
|
|
@@ -1168,7 +1158,7 @@ class _NumbersModel(Cacheable):
|
|
|
1168
1158
|
)
|
|
1169
1159
|
|
|
1170
1160
|
def add_sheet(self, sheet_name: str) -> int:
|
|
1171
|
-
"""Add a new sheet with a copy of a table from another sheet"""
|
|
1161
|
+
"""Add a new sheet with a copy of a table from another sheet."""
|
|
1172
1162
|
sheet_id, _ = self.objects.create_object_from_dict(
|
|
1173
1163
|
"Document", {"name": sheet_name}, TNArchives.SheetArchive
|
|
1174
1164
|
)
|
|
@@ -1319,7 +1309,8 @@ class _NumbersModel(Cacheable):
|
|
|
1319
1309
|
|
|
1320
1310
|
def update_paragraph_styles(self):
|
|
1321
1311
|
"""Create new paragraph style archives for any new styles that
|
|
1322
|
-
have been created for this document
|
|
1312
|
+
have been created for this document.
|
|
1313
|
+
"""
|
|
1323
1314
|
new_styles = [x for x in self.styles.values() if x._text_style_obj_id is None]
|
|
1324
1315
|
updated_styles = [
|
|
1325
1316
|
x
|
|
@@ -1335,10 +1326,11 @@ class _NumbersModel(Cacheable):
|
|
|
1335
1326
|
|
|
1336
1327
|
def update_cell_styles(self, table_id: int, data: List):
|
|
1337
1328
|
"""Create new cell style archives for any cells whose styles
|
|
1338
|
-
have changes that require a cell style
|
|
1329
|
+
have changes that require a cell style.
|
|
1330
|
+
"""
|
|
1339
1331
|
cell_styles = {}
|
|
1340
|
-
for
|
|
1341
|
-
for
|
|
1332
|
+
for _, row in enumerate(data):
|
|
1333
|
+
for _, cell in enumerate(row):
|
|
1342
1334
|
if cell._style is not None and cell._style._update_cell_style:
|
|
1343
1335
|
fingerprint = (
|
|
1344
1336
|
str(cell.style.alignment.vertical)
|
|
@@ -1424,7 +1416,8 @@ class _NumbersModel(Cacheable):
|
|
|
1424
1416
|
|
|
1425
1417
|
def custom_style_name(self) -> Tuple[str, str]:
|
|
1426
1418
|
"""Find custom styles in the current document and return the next
|
|
1427
|
-
highest numbered style
|
|
1419
|
+
highest numbered style.
|
|
1420
|
+
"""
|
|
1428
1421
|
stylesheet_id = self.objects[DOCUMENT_ID].stylesheet.identifier
|
|
1429
1422
|
current_styles = self.styles.keys()
|
|
1430
1423
|
custom_styles = [x for x in current_styles if re.fullmatch(r"Custom Style \d+", x)]
|
|
@@ -1441,10 +1434,10 @@ class _NumbersModel(Cacheable):
|
|
|
1441
1434
|
else:
|
|
1442
1435
|
return "Custom Style 1"
|
|
1443
1436
|
|
|
1444
|
-
def pack_cell_storage(
|
|
1437
|
+
def pack_cell_storage(
|
|
1445
1438
|
self, table_id: int, data: List, row_num: int, col_num: int
|
|
1446
1439
|
) -> bytearray:
|
|
1447
|
-
"""Create a storage buffer for a cell using v5 (modern) layout"""
|
|
1440
|
+
"""Create a storage buffer for a cell using v5 (modern) layout."""
|
|
1448
1441
|
cell = data[row_num][col_num]
|
|
1449
1442
|
if cell._style is not None:
|
|
1450
1443
|
if cell._style._text_style_obj_id is not None:
|
|
@@ -1514,6 +1507,7 @@ class _NumbersModel(Cacheable):
|
|
|
1514
1507
|
warn(
|
|
1515
1508
|
f"@{table_name}:[{row_num},{col_num}]: unsupported data type {data_type} for save",
|
|
1516
1509
|
UnsupportedWarning,
|
|
1510
|
+
stacklevel=1,
|
|
1517
1511
|
)
|
|
1518
1512
|
return None
|
|
1519
1513
|
|
|
@@ -1580,19 +1574,16 @@ class _NumbersModel(Cacheable):
|
|
|
1580
1574
|
return TableFormulas(self, table_id)
|
|
1581
1575
|
|
|
1582
1576
|
@cache(num_args=3)
|
|
1583
|
-
def table_cell_decode(self, table_id: int, row_num: int, col_num: int) -> Dict:
|
|
1577
|
+
def table_cell_decode(self, table_id: int, row_num: int, col_num: int) -> Dict:
|
|
1584
1578
|
buffer = self.storage_buffer(table_id, row_num, col_num)
|
|
1585
1579
|
if buffer is None:
|
|
1586
1580
|
return None
|
|
1587
1581
|
|
|
1588
|
-
|
|
1589
|
-
return cell
|
|
1582
|
+
return CellStorage(self, table_id, buffer, row_num, col_num)
|
|
1590
1583
|
|
|
1591
1584
|
@cache(num_args=2)
|
|
1592
1585
|
def table_rich_text(self, table_id: int, string_key: int) -> Dict:
|
|
1593
|
-
"""
|
|
1594
|
-
Extract bullets and hyperlinks from a rich text data cell.
|
|
1595
|
-
"""
|
|
1586
|
+
"""Extract bullets and hyperlinks from a rich text data cell."""
|
|
1596
1587
|
# The table model base data store contains a richTextTable field
|
|
1597
1588
|
# which is a reference to a TST.TableDataList. The TableDataList
|
|
1598
1589
|
# has a list of payloads in a field called entries. This will be
|
|
@@ -1675,7 +1666,7 @@ class _NumbersModel(Cacheable):
|
|
|
1675
1666
|
|
|
1676
1667
|
return {
|
|
1677
1668
|
"text": cell_text,
|
|
1678
|
-
"bulleted": any(
|
|
1669
|
+
"bulleted": any(c is not None for c in bullet_chars),
|
|
1679
1670
|
"bullets": bullets,
|
|
1680
1671
|
"bullet_chars": bullet_chars,
|
|
1681
1672
|
"hyperlinks": hyperlinks,
|
|
@@ -1683,14 +1674,15 @@ class _NumbersModel(Cacheable):
|
|
|
1683
1674
|
|
|
1684
1675
|
def cell_text_style(self, cell_storage: object) -> object:
|
|
1685
1676
|
"""Return the text style object for the cell or, if none
|
|
1686
|
-
is defined, the default header, footer or body style
|
|
1677
|
+
is defined, the default header, footer or body style.
|
|
1678
|
+
"""
|
|
1687
1679
|
if cell_storage.text_style_id is not None:
|
|
1688
1680
|
return self.table_style(cell_storage.table_id, cell_storage.text_style_id)
|
|
1689
1681
|
|
|
1690
1682
|
table_model = self.objects[cell_storage.table_id]
|
|
1691
|
-
if cell_storage.row_num in range(
|
|
1683
|
+
if cell_storage.row_num in range(table_model.number_of_header_rows):
|
|
1692
1684
|
return self.objects[table_model.header_row_text_style.identifier]
|
|
1693
|
-
elif cell_storage.col_num in range(
|
|
1685
|
+
elif cell_storage.col_num in range(table_model.number_of_header_columns):
|
|
1694
1686
|
return self.objects[table_model.header_column_text_style.identifier]
|
|
1695
1687
|
elif table_model.number_of_footer_rows > 0:
|
|
1696
1688
|
start_row_num = table_model.number_of_rows - table_model.number_of_footer_rows
|
|
@@ -1724,7 +1716,8 @@ class _NumbersModel(Cacheable):
|
|
|
1724
1716
|
|
|
1725
1717
|
def char_property(self, style: object, field: str):
|
|
1726
1718
|
"""Return a char_property field from a style if present
|
|
1727
|
-
in the style, or from the parent if not
|
|
1719
|
+
in the style, or from the parent if not.
|
|
1720
|
+
"""
|
|
1728
1721
|
if not style.char_properties.HasField(field):
|
|
1729
1722
|
parent = self.objects[style.super.parent.identifier]
|
|
1730
1723
|
return getattr(parent.char_properties, field)
|
|
@@ -1733,7 +1726,8 @@ class _NumbersModel(Cacheable):
|
|
|
1733
1726
|
|
|
1734
1727
|
def para_property(self, style: object, field: str) -> float:
|
|
1735
1728
|
"""Return a para_property field from a style if present
|
|
1736
|
-
in the style, or from the parent if not
|
|
1729
|
+
in the style, or from the parent if not.
|
|
1730
|
+
"""
|
|
1737
1731
|
if not style.para_properties.HasField(field):
|
|
1738
1732
|
parent = self.objects[style.super.parent.identifier]
|
|
1739
1733
|
return getattr(parent.para_properties, field)
|
|
@@ -1742,7 +1736,8 @@ class _NumbersModel(Cacheable):
|
|
|
1742
1736
|
|
|
1743
1737
|
def cell_property(self, style: object, field: str) -> float:
|
|
1744
1738
|
"""Return a cell_property field from a style if present
|
|
1745
|
-
in the style, or from the parent if not
|
|
1739
|
+
in the style, or from the parent if not.
|
|
1740
|
+
"""
|
|
1746
1741
|
if not style.cell_properties.HasField(field):
|
|
1747
1742
|
parent = self.objects[style.super.parent.identifier]
|
|
1748
1743
|
return getattr(parent.cell_properties, field)
|
|
@@ -1803,8 +1798,7 @@ class _NumbersModel(Cacheable):
|
|
|
1803
1798
|
style = self.table_style(cell_storage.table_id, cell_storage.cell_style_id)
|
|
1804
1799
|
padding = self.cell_property(style, "padding")
|
|
1805
1800
|
# Padding is always identical (only one UI setting)
|
|
1806
|
-
|
|
1807
|
-
return text_inset
|
|
1801
|
+
return padding.left
|
|
1808
1802
|
|
|
1809
1803
|
def cell_text_wrap(self, cell_storage: CellStorage) -> float:
|
|
1810
1804
|
if cell_storage.cell_style_id is None:
|
|
@@ -1814,7 +1808,7 @@ class _NumbersModel(Cacheable):
|
|
|
1814
1808
|
return self.cell_property(style, "text_wrap")
|
|
1815
1809
|
|
|
1816
1810
|
def stroke_type(self, stroke_run: object) -> str:
|
|
1817
|
-
"""Return the stroke type for a stroke run"""
|
|
1811
|
+
"""Return the stroke type for a stroke run."""
|
|
1818
1812
|
stroke_type = stroke_run.stroke.pattern.type
|
|
1819
1813
|
if stroke_type == StrokePattern.StrokePatternType.TSDSolidPattern:
|
|
1820
1814
|
return "solid"
|
|
@@ -1842,7 +1836,7 @@ class _NumbersModel(Cacheable):
|
|
|
1842
1836
|
):
|
|
1843
1837
|
return cell
|
|
1844
1838
|
elif cell.is_merged:
|
|
1845
|
-
if side
|
|
1839
|
+
if side in ["top", "left"]:
|
|
1846
1840
|
return cell
|
|
1847
1841
|
else:
|
|
1848
1842
|
return cell
|
|
@@ -1851,7 +1845,7 @@ class _NumbersModel(Cacheable):
|
|
|
1851
1845
|
def set_cell_border(
|
|
1852
1846
|
self, table_id: int, row_num: int, col_num: int, side: str, border_value: Border
|
|
1853
1847
|
):
|
|
1854
|
-
"""Set the 2 borders adjacent to a stroke if within the table range"""
|
|
1848
|
+
"""Set the 2 borders adjacent to a stroke if within the table range."""
|
|
1855
1849
|
if side == "top":
|
|
1856
1850
|
if (cell := self.cell_for_stroke(table_id, "top", row_num, col_num)) is not None:
|
|
1857
1851
|
cell._border.top = border_value
|
|
@@ -2039,12 +2033,12 @@ class _NumbersModel(Cacheable):
|
|
|
2039
2033
|
|
|
2040
2034
|
|
|
2041
2035
|
def rgb(obj) -> RGB:
|
|
2042
|
-
"""Convert a TSPArchives.Color into an RGB tuple"""
|
|
2036
|
+
"""Convert a TSPArchives.Color into an RGB tuple."""
|
|
2043
2037
|
return RGB(round(obj.r * 255), round(obj.g * 255), round(obj.b * 255))
|
|
2044
2038
|
|
|
2045
2039
|
|
|
2046
2040
|
def range_end(obj):
|
|
2047
|
-
"""Select end range for a IndexSetArchive.IndexSetEntry"""
|
|
2041
|
+
"""Select end range for a IndexSetArchive.IndexSetEntry."""
|
|
2048
2042
|
if obj.HasField("range_end"):
|
|
2049
2043
|
return obj.range_end
|
|
2050
2044
|
else:
|
|
@@ -2052,7 +2046,7 @@ def range_end(obj):
|
|
|
2052
2046
|
|
|
2053
2047
|
|
|
2054
2048
|
def formatted_number(number_type, index):
|
|
2055
|
-
"""Returns the numbered index bullet formatted for different types"""
|
|
2049
|
+
"""Returns the numbered index bullet formatted for different types."""
|
|
2056
2050
|
bullet_char = BULLET_PREFIXES[number_type]
|
|
2057
2051
|
bullet_char += BULLET_CONVERSION[number_type](index)
|
|
2058
2052
|
bullet_char += BULLET_SUFFIXES[number_type]
|
|
@@ -2061,10 +2055,7 @@ def formatted_number(number_type, index):
|
|
|
2061
2055
|
|
|
2062
2056
|
|
|
2063
2057
|
def node_to_col_ref(node: object, table_name: str, col_num: int) -> str:
|
|
2064
|
-
if node.AST_column.absolute
|
|
2065
|
-
col = node.AST_column.column
|
|
2066
|
-
else:
|
|
2067
|
-
col = col_num + node.AST_column.column
|
|
2058
|
+
col = node.AST_column.column if node.AST_column.absolute else col_num + node.AST_column.column
|
|
2068
2059
|
|
|
2069
2060
|
col_name = xl_col_to_name(col, node.AST_column.absolute)
|
|
2070
2061
|
if table_name is not None:
|
|
@@ -2074,10 +2065,7 @@ def node_to_col_ref(node: object, table_name: str, col_num: int) -> str:
|
|
|
2074
2065
|
|
|
2075
2066
|
|
|
2076
2067
|
def node_to_row_ref(node: object, table_name: str, row_num: int) -> str:
|
|
2077
|
-
if node.AST_row.absolute
|
|
2078
|
-
row = node.AST_row.row
|
|
2079
|
-
else:
|
|
2080
|
-
row = row_num + node.AST_row.row
|
|
2068
|
+
row = node.AST_row.row if node.AST_row.absolute else row_num + node.AST_row.row
|
|
2081
2069
|
|
|
2082
2070
|
row_name = f"${row+1}" if node.AST_row.absolute else f"{row+1}"
|
|
2083
2071
|
if table_name is not None:
|
|
@@ -2087,14 +2075,8 @@ def node_to_row_ref(node: object, table_name: str, row_num: int) -> str:
|
|
|
2087
2075
|
|
|
2088
2076
|
|
|
2089
2077
|
def node_to_row_col_ref(node: object, table_name: str, row_num: int, col_num: int) -> str:
|
|
2090
|
-
if node.AST_row.absolute
|
|
2091
|
-
|
|
2092
|
-
else:
|
|
2093
|
-
row = row_num + node.AST_row.row
|
|
2094
|
-
if node.AST_column.absolute:
|
|
2095
|
-
col = node.AST_column.column
|
|
2096
|
-
else:
|
|
2097
|
-
col = col_num + node.AST_column.column
|
|
2078
|
+
row = node.AST_row.row if node.AST_row.absolute else row_num + node.AST_row.row
|
|
2079
|
+
col = node.AST_column.column if node.AST_column.absolute else col_num + node.AST_column.column
|
|
2098
2080
|
|
|
2099
2081
|
ref = xl_rowcol_to_cell(
|
|
2100
2082
|
row,
|
|
@@ -2111,8 +2093,7 @@ def node_to_row_col_ref(node: object, table_name: str, row_num: int, col_num: in
|
|
|
2111
2093
|
def get_storage_buffers_for_row(
|
|
2112
2094
|
storage_buffer: bytes, offsets: list, num_cols: int, has_wide_offsets: bool
|
|
2113
2095
|
) -> List[bytes]:
|
|
2114
|
-
"""
|
|
2115
|
-
Extract storage buffers for each cell in a table row
|
|
2096
|
+
"""Extract storage buffers for each cell in a table row.
|
|
2116
2097
|
|
|
2117
2098
|
Args:
|
|
2118
2099
|
storage_buffer: cell_storage_buffer or cell_storage_buffer for a table row
|
|
@@ -2155,7 +2136,8 @@ def get_storage_buffers_for_row(
|
|
|
2155
2136
|
|
|
2156
2137
|
def clear_field_container(obj):
|
|
2157
2138
|
"""Remove all entries from a protobuf RepeatedCompositeFieldContainer
|
|
2158
|
-
in a portable fashion
|
|
2139
|
+
in a portable fashion.
|
|
2140
|
+
"""
|
|
2159
2141
|
while len(obj) > 0:
|
|
2160
2142
|
_ = obj.pop()
|
|
2161
2143
|
|
|
@@ -2178,10 +2160,9 @@ def pack_decimal128(value: float) -> bytearray:
|
|
|
2178
2160
|
|
|
2179
2161
|
|
|
2180
2162
|
def field_references(obj: object) -> dict:
|
|
2181
|
-
"""Return a dict of all fields in an object that are references to other objects"""
|
|
2182
|
-
|
|
2163
|
+
"""Return a dict of all fields in an object that are references to other objects."""
|
|
2164
|
+
return {
|
|
2183
2165
|
x[0].name: {"identifier": getattr(obj, x[0].name).identifier}
|
|
2184
2166
|
for x in obj.ListFields()
|
|
2185
2167
|
if type(getattr(obj, x[0].name)) == TSPMessages.Reference
|
|
2186
2168
|
}
|
|
2187
|
-
return refs
|
numbers_parser/numbers_cache.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
from functools import wraps
|
|
2
1
|
from collections import defaultdict
|
|
2
|
+
from functools import wraps
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class Cacheable:
|
|
@@ -11,7 +11,8 @@ class Cacheable:
|
|
|
11
11
|
|
|
12
12
|
def cache(num_args=1):
|
|
13
13
|
"""Decorator to memoize a class method using a precise subset of
|
|
14
|
-
its arguments
|
|
14
|
+
its arguments.
|
|
15
|
+
"""
|
|
15
16
|
|
|
16
17
|
def cache_decorator(func):
|
|
17
18
|
@wraps(func)
|