numbers-parser 4.13.3__py3-none-any.whl → 4.14.2__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 +5 -4
- numbers_parser/_cat_numbers.py +24 -16
- numbers_parser/_csv2numbers.py +13 -14
- numbers_parser/_unpack_numbers.py +6 -7
- numbers_parser/bullets.py +7 -8
- numbers_parser/cell.py +280 -255
- numbers_parser/constants.py +22 -8
- numbers_parser/containers.py +11 -10
- numbers_parser/document.py +196 -150
- numbers_parser/exceptions.py +1 -8
- numbers_parser/formula.py +29 -32
- numbers_parser/generated/TSKArchives_pb2.py +92 -92
- numbers_parser/generated/TSSArchives_pb2.py +36 -36
- numbers_parser/generated/TSWPCommandArchives_pb2.py +99 -99
- numbers_parser/generated/fontmap.py +16 -10
- numbers_parser/generated/mapping.py +0 -1
- numbers_parser/iwafile.py +16 -16
- numbers_parser/iwork.py +32 -17
- numbers_parser/model.py +222 -210
- numbers_parser/numbers_cache.py +6 -7
- numbers_parser/numbers_uuid.py +4 -1
- numbers_parser/roman.py +32 -0
- {numbers_parser-4.13.3.dist-info → numbers_parser-4.14.2.dist-info}/METADATA +18 -22
- {numbers_parser-4.13.3.dist-info → numbers_parser-4.14.2.dist-info}/RECORD +27 -26
- {numbers_parser-4.13.3.dist-info → numbers_parser-4.14.2.dist-info}/WHEEL +1 -1
- {numbers_parser-4.13.3.dist-info → numbers_parser-4.14.2.dist-info}/LICENSE.rst +0 -0
- {numbers_parser-4.13.3.dist-info → numbers_parser-4.14.2.dist-info}/entry_points.txt +0 -0
numbers_parser/model.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import re
|
|
2
4
|
from array import array
|
|
3
5
|
from collections import defaultdict
|
|
@@ -5,7 +7,6 @@ from hashlib import sha1
|
|
|
5
7
|
from math import floor
|
|
6
8
|
from pathlib import Path
|
|
7
9
|
from struct import pack
|
|
8
|
-
from typing import Dict, List, Optional, Tuple, Union
|
|
9
10
|
|
|
10
11
|
from numbers_parser.bullets import (
|
|
11
12
|
BULLET_CONVERSION,
|
|
@@ -91,27 +92,27 @@ class MergeCells:
|
|
|
91
92
|
def __init__(self) -> None:
|
|
92
93
|
self._references = defaultdict(lambda: False)
|
|
93
94
|
|
|
94
|
-
def add_reference(self, row: int, col: int, rect:
|
|
95
|
+
def add_reference(self, row: int, col: int, rect: tuple) -> None:
|
|
95
96
|
self._references[(row, col)] = MergeReference(*rect)
|
|
96
97
|
|
|
97
|
-
def add_anchor(self, row: int, col: int, size:
|
|
98
|
+
def add_anchor(self, row: int, col: int, size: tuple) -> None:
|
|
98
99
|
self._references[(row, col)] = MergeAnchor(size)
|
|
99
100
|
|
|
100
|
-
def is_merge_reference(self, row_col:
|
|
101
|
+
def is_merge_reference(self, row_col: tuple) -> bool:
|
|
101
102
|
# defaultdict will default this to False for missing entries
|
|
102
103
|
return isinstance(self._references[row_col], MergeReference)
|
|
103
104
|
|
|
104
|
-
def is_merge_anchor(self, row_col:
|
|
105
|
+
def is_merge_anchor(self, row_col: tuple) -> bool:
|
|
105
106
|
# defaultdict will default this to False for missing entries
|
|
106
107
|
return isinstance(self._references[row_col], MergeAnchor)
|
|
107
108
|
|
|
108
|
-
def get(self, row_col:
|
|
109
|
+
def get(self, row_col: tuple) -> MergeAnchor | MergeReference:
|
|
109
110
|
return self._references[row_col]
|
|
110
111
|
|
|
111
|
-
def size(self, row_col:
|
|
112
|
+
def size(self, row_col: tuple) -> tuple:
|
|
112
113
|
return self._references[row_col].size
|
|
113
114
|
|
|
114
|
-
def rect(self, row_col:
|
|
115
|
+
def rect(self, row_col: tuple) -> tuple:
|
|
115
116
|
return self._references[row_col].rect
|
|
116
117
|
|
|
117
118
|
def merge_cells(self):
|
|
@@ -121,14 +122,14 @@ class MergeCells:
|
|
|
121
122
|
class DataLists(Cacheable):
|
|
122
123
|
"""Model for TST.DataList with caching and key generation for new values."""
|
|
123
124
|
|
|
124
|
-
def __init__(self, model: object, datalist_name: str, value_attr:
|
|
125
|
+
def __init__(self, model: object, datalist_name: str, value_attr: str | None = None) -> None:
|
|
125
126
|
self._model = model
|
|
126
127
|
self._datalists = {}
|
|
127
128
|
self._value_attr = value_attr
|
|
128
129
|
self._datalist_name = datalist_name
|
|
129
130
|
|
|
130
131
|
@cache()
|
|
131
|
-
def add_table(self, table_id: int):
|
|
132
|
+
def add_table(self, table_id: int) -> None:
|
|
132
133
|
"""Cache a new datalist for a table if not already seen."""
|
|
133
134
|
base_data_store = self._model.objects[table_id].base_data_store
|
|
134
135
|
datalist_id = getattr(base_data_store, self._datalist_name).identifier
|
|
@@ -162,10 +163,9 @@ class DataLists(Cacheable):
|
|
|
162
163
|
def value_key(self, value):
|
|
163
164
|
if hasattr(value, "DESCRIPTOR"):
|
|
164
165
|
return repr(value)
|
|
165
|
-
|
|
166
|
-
return value
|
|
166
|
+
return value
|
|
167
167
|
|
|
168
|
-
def init(self, table_id: int):
|
|
168
|
+
def init(self, table_id: int) -> None:
|
|
169
169
|
"""Remove all entries from a datalist."""
|
|
170
170
|
self.add_table(table_id)
|
|
171
171
|
self._datalists[table_id]["by_key"] = {}
|
|
@@ -176,7 +176,8 @@ class DataLists(Cacheable):
|
|
|
176
176
|
clear_field_container(self._datalists[table_id]["datalist"].entries)
|
|
177
177
|
|
|
178
178
|
def lookup_key(self, table_id: int, value) -> int:
|
|
179
|
-
"""
|
|
179
|
+
"""
|
|
180
|
+
Return the key associated with a value for a particular table entry.
|
|
180
181
|
If the value is not in the datalist, allocate a new entry with the
|
|
181
182
|
next available key.
|
|
182
183
|
"""
|
|
@@ -204,7 +205,8 @@ class DataLists(Cacheable):
|
|
|
204
205
|
|
|
205
206
|
|
|
206
207
|
class _NumbersModel(Cacheable):
|
|
207
|
-
"""
|
|
208
|
+
"""
|
|
209
|
+
Loads all objects from Numbers document and provides decoding
|
|
208
210
|
methods for other classes in the module to abstract away the
|
|
209
211
|
internal structures of Numbers document data structures.
|
|
210
212
|
|
|
@@ -248,13 +250,11 @@ class _NumbersModel(Cacheable):
|
|
|
248
250
|
if value is None:
|
|
249
251
|
if sheet_id not in self.objects:
|
|
250
252
|
return None
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
self.objects[sheet_id].name = value
|
|
255
|
-
return None
|
|
253
|
+
return self.objects[sheet_id].name
|
|
254
|
+
self.objects[sheet_id].name = value
|
|
255
|
+
return None
|
|
256
256
|
|
|
257
|
-
def set_table_data(self, table_id: int, data:
|
|
257
|
+
def set_table_data(self, table_id: int, data: list) -> None:
|
|
258
258
|
self._table_data[table_id] = data
|
|
259
259
|
|
|
260
260
|
# Don't cache: new tables can be added at runtime
|
|
@@ -313,27 +313,25 @@ class _NumbersModel(Cacheable):
|
|
|
313
313
|
def table_name(self, table_id, value=None):
|
|
314
314
|
if value is None:
|
|
315
315
|
return self.objects[table_id].table_name
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
return None
|
|
316
|
+
self.objects[table_id].table_name = value
|
|
317
|
+
return None
|
|
319
318
|
|
|
320
|
-
def table_name_enabled(self, table_id: int, enabled:
|
|
319
|
+
def table_name_enabled(self, table_id: int, enabled: bool | None = None):
|
|
321
320
|
if enabled is not None:
|
|
322
321
|
self.objects[table_id].table_name_enabled = enabled
|
|
323
322
|
return None
|
|
324
|
-
|
|
325
|
-
return self.objects[table_id].table_name_enabled
|
|
323
|
+
return self.objects[table_id].table_name_enabled
|
|
326
324
|
|
|
327
|
-
def caption_enabled(self, table_id: int, enabled:
|
|
325
|
+
def caption_enabled(self, table_id: int, enabled: bool | None = None) -> bool:
|
|
328
326
|
table_info = self.objects[self.table_info_id(table_id)]
|
|
329
327
|
if enabled is not None:
|
|
330
328
|
table_info.super.caption_hidden = not enabled
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
329
|
+
return None
|
|
330
|
+
caption_info_id = table_info.super.caption.identifier
|
|
331
|
+
caption_archive = self.objects[caption_info_id]
|
|
332
|
+
if caption_archive.DESCRIPTOR.name == "StandinCaptionArchive":
|
|
333
|
+
return False
|
|
334
|
+
return not table_info.super.caption_hidden
|
|
337
335
|
|
|
338
336
|
def find_style_id(self, style_substr: str):
|
|
339
337
|
stylesheet = self.objects[self.stylesheet_id()]
|
|
@@ -344,18 +342,18 @@ class _NumbersModel(Cacheable):
|
|
|
344
342
|
|
|
345
343
|
def caption_paragraph_style_id(self):
|
|
346
344
|
style_map = {
|
|
347
|
-
|
|
348
|
-
for
|
|
349
|
-
if "Caption" in self.objects[
|
|
345
|
+
x: self.objects[x]
|
|
346
|
+
for x in self.find_refs("ParagraphStyleArchive")
|
|
347
|
+
if "Caption" in self.objects[x].super.name
|
|
350
348
|
}
|
|
351
|
-
return
|
|
349
|
+
return next(iter(style_map.keys()))
|
|
352
350
|
|
|
353
351
|
@cache(num_args=0)
|
|
354
352
|
def stylesheet_id(self):
|
|
355
353
|
return self.find_refs("StylesheetArchive")[0]
|
|
356
354
|
|
|
357
|
-
def set_reference(self, obj: object,
|
|
358
|
-
obj.MergeFrom(TSPMessages.Reference(identifier=
|
|
355
|
+
def set_reference(self, obj: object, ref_id: int) -> None:
|
|
356
|
+
obj.MergeFrom(TSPMessages.Reference(identifier=ref_id))
|
|
359
357
|
|
|
360
358
|
def create_path_source_archive(self, table_id):
|
|
361
359
|
box_size = 100.0
|
|
@@ -389,12 +387,12 @@ class _NumbersModel(Cacheable):
|
|
|
389
387
|
type=TSPMessages.Path.ElementType.moveTo,
|
|
390
388
|
points=[TSPMessages.Point(x=0.0, y=0.0)],
|
|
391
389
|
),
|
|
392
|
-
]
|
|
390
|
+
],
|
|
393
391
|
),
|
|
394
392
|
),
|
|
395
393
|
)
|
|
396
394
|
|
|
397
|
-
def create_caption_archive(self, table_id):
|
|
395
|
+
def create_caption_archive(self, table_id) -> None:
|
|
398
396
|
table_info_id = self.table_info_id(table_id)
|
|
399
397
|
table_info = self.objects[table_info_id]
|
|
400
398
|
caption_placement_id, _ = self.objects.create_object_from_dict(
|
|
@@ -421,16 +419,16 @@ class _NumbersModel(Cacheable):
|
|
|
421
419
|
{
|
|
422
420
|
"character_index": 0,
|
|
423
421
|
"object": {"identifier": self.caption_paragraph_style_id()},
|
|
424
|
-
}
|
|
425
|
-
]
|
|
422
|
+
},
|
|
423
|
+
],
|
|
426
424
|
},
|
|
427
425
|
"table_list_style": {
|
|
428
426
|
"entries": [
|
|
429
427
|
{
|
|
430
428
|
"character_index": 0,
|
|
431
429
|
"object": {"identifier": self.find_style_id("liststyle")},
|
|
432
|
-
}
|
|
433
|
-
]
|
|
430
|
+
},
|
|
431
|
+
],
|
|
434
432
|
},
|
|
435
433
|
"table_para_starts": {"entries": [{"character_index": 0, "first": 0, "second": 0}]},
|
|
436
434
|
"table_para_bidi": {"entries": [{"character_index": 0, "first": 0, "second": 0}]},
|
|
@@ -445,7 +443,9 @@ class _NumbersModel(Cacheable):
|
|
|
445
443
|
self.caption_paragraph_style_id(),
|
|
446
444
|
]:
|
|
447
445
|
self.add_component_reference(
|
|
448
|
-
object_id,
|
|
446
|
+
object_id,
|
|
447
|
+
location="CalculationEngine",
|
|
448
|
+
component_id=self.stylesheet_id(),
|
|
449
449
|
)
|
|
450
450
|
caption_info.super.MergeFrom(
|
|
451
451
|
TSWPArchives.ShapeInfoArchive(
|
|
@@ -458,18 +458,19 @@ class _NumbersModel(Cacheable):
|
|
|
458
458
|
strokePatternOffsetDistance=0.0,
|
|
459
459
|
pathsource=self.create_path_source_archive(table_id),
|
|
460
460
|
),
|
|
461
|
-
)
|
|
461
|
+
),
|
|
462
462
|
)
|
|
463
463
|
|
|
464
464
|
self.set_reference(table_info.super.caption, caption_info_id)
|
|
465
465
|
component = self.metadata_component(self.calc_engine_id())
|
|
466
466
|
component.object_uuid_map_entries.append(
|
|
467
467
|
TSPArchiveMessages.ObjectUUIDMapEntry(
|
|
468
|
-
identifier=caption_info_id,
|
|
469
|
-
|
|
468
|
+
identifier=caption_info_id,
|
|
469
|
+
uuid=NumbersUUID().protobuf2,
|
|
470
|
+
),
|
|
470
471
|
)
|
|
471
472
|
|
|
472
|
-
def caption_text(self, table_id: int, caption: str = None) -> str:
|
|
473
|
+
def caption_text(self, table_id: int, caption: str | None = None) -> str:
|
|
473
474
|
table_info = self.objects[self.table_info_id(table_id)]
|
|
474
475
|
caption_info_id = table_info.super.caption.identifier
|
|
475
476
|
caption_archive = self.objects[caption_info_id]
|
|
@@ -477,19 +478,18 @@ class _NumbersModel(Cacheable):
|
|
|
477
478
|
if caption_archive.DESCRIPTOR.name == "StandinCaptionArchive":
|
|
478
479
|
if caption is None:
|
|
479
480
|
return "Caption"
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
caption_archive = self.objects[caption_info_id]
|
|
481
|
+
self.create_caption_archive(table_id)
|
|
482
|
+
caption_info_id = table_info.super.caption.identifier
|
|
483
|
+
caption_archive = self.objects[caption_info_id]
|
|
484
484
|
|
|
485
485
|
caption_storage_id = caption_archive.super.owned_storage.identifier
|
|
486
486
|
if caption is not None:
|
|
487
487
|
clear_field_container(self.objects[caption_storage_id].text)
|
|
488
488
|
self.objects[caption_storage_id].text.append(caption)
|
|
489
|
-
|
|
489
|
+
return None
|
|
490
|
+
if len(self.objects[caption_storage_id].text) == 0:
|
|
490
491
|
return "Caption"
|
|
491
|
-
|
|
492
|
-
return self.objects[caption_storage_id].text[0]
|
|
492
|
+
return self.objects[caption_storage_id].text[0]
|
|
493
493
|
|
|
494
494
|
@cache()
|
|
495
495
|
def table_tiles(self, table_id):
|
|
@@ -511,17 +511,17 @@ class _NumbersModel(Cacheable):
|
|
|
511
511
|
return self._table_formats.lookup_value(table_id, key).format
|
|
512
512
|
|
|
513
513
|
@cache(num_args=3)
|
|
514
|
-
def format_archive(self, table_id: int, format_type: FormattingType,
|
|
514
|
+
def format_archive(self, table_id: int, format_type: FormattingType, formatting: Formatting):
|
|
515
515
|
"""Create a table format from a Formatting spec and return the table format ID."""
|
|
516
|
-
attrs = {x: getattr(
|
|
516
|
+
attrs = {x: getattr(formatting, x) for x in ALLOWED_FORMATTING_PARAMETERS[format_type]}
|
|
517
517
|
attrs["format_type"] = FORMAT_TYPE_MAP[format_type]
|
|
518
518
|
|
|
519
|
-
|
|
520
|
-
return self._table_formats.lookup_key(table_id,
|
|
519
|
+
format_archive = TSKArchives.FormatStructArchive(**attrs)
|
|
520
|
+
return self._table_formats.lookup_key(table_id, format_archive)
|
|
521
521
|
|
|
522
|
-
def cell_popup_model(self, parent_id: int,
|
|
522
|
+
def cell_popup_model(self, parent_id: int, formatting: Formatting):
|
|
523
523
|
tsce_items = [{"cell_value_type": "NIL_TYPE"}]
|
|
524
|
-
for item in
|
|
524
|
+
for item in formatting.popup_values:
|
|
525
525
|
if isinstance(item, str):
|
|
526
526
|
tsce_items.append(
|
|
527
527
|
{
|
|
@@ -550,7 +550,12 @@ class _NumbersModel(Cacheable):
|
|
|
550
550
|
)
|
|
551
551
|
return popup_menu_id
|
|
552
552
|
|
|
553
|
-
def control_cell_archive(
|
|
553
|
+
def control_cell_archive(
|
|
554
|
+
self,
|
|
555
|
+
table_id: int,
|
|
556
|
+
format_type: FormattingType,
|
|
557
|
+
formatting: Formatting,
|
|
558
|
+
):
|
|
554
559
|
"""Create control cell archive from a Formatting spec and return the table format ID."""
|
|
555
560
|
if format_type == FormattingType.TICKBOX:
|
|
556
561
|
cell_spec = TSTArchives.CellSpecArchive(interaction_type=CellInteractionType.TOGGLE)
|
|
@@ -564,26 +569,26 @@ class _NumbersModel(Cacheable):
|
|
|
564
569
|
elif format_type == FormattingType.SLIDER:
|
|
565
570
|
cell_spec = TSTArchives.CellSpecArchive(
|
|
566
571
|
interaction_type=CellInteractionType.SLIDER,
|
|
567
|
-
range_control_min=
|
|
568
|
-
range_control_max=
|
|
569
|
-
range_control_inc=
|
|
572
|
+
range_control_min=formatting.minimum,
|
|
573
|
+
range_control_max=formatting.maximum,
|
|
574
|
+
range_control_inc=formatting.increment,
|
|
570
575
|
)
|
|
571
576
|
else: # POPUP
|
|
572
|
-
popup_id = self.cell_popup_model(self._control_specs.id(table_id),
|
|
577
|
+
popup_id = self.cell_popup_model(self._control_specs.id(table_id), formatting)
|
|
573
578
|
cell_spec = TSTArchives.CellSpecArchive(
|
|
574
579
|
interaction_type=CellInteractionType.POPUP,
|
|
575
580
|
chooser_control_popup_model=TSPMessages.Reference(identifier=popup_id),
|
|
576
|
-
chooser_control_start_w_first=not (
|
|
581
|
+
chooser_control_start_w_first=not (formatting.allow_none),
|
|
577
582
|
)
|
|
578
583
|
return self._control_specs.lookup_key(table_id, cell_spec)
|
|
579
584
|
|
|
580
|
-
def add_custom_decimal_format_archive(self,
|
|
585
|
+
def add_custom_decimal_format_archive(self, formatting: CustomFormatting) -> None:
|
|
581
586
|
"""Create a custom format from the format spec."""
|
|
582
|
-
integer_format =
|
|
583
|
-
decimal_format =
|
|
584
|
-
num_integers =
|
|
585
|
-
num_decimals =
|
|
586
|
-
show_thousands_separator =
|
|
587
|
+
integer_format = formatting.integer_format
|
|
588
|
+
decimal_format = formatting.decimal_format
|
|
589
|
+
num_integers = formatting.num_integers
|
|
590
|
+
num_decimals = formatting.num_decimals
|
|
591
|
+
show_thousands_separator = formatting.show_thousands_separator
|
|
587
592
|
|
|
588
593
|
if num_integers == 0:
|
|
589
594
|
format_string = ""
|
|
@@ -618,7 +623,7 @@ class _NumbersModel(Cacheable):
|
|
|
618
623
|
)
|
|
619
624
|
|
|
620
625
|
format_archive = TSKArchives.CustomFormatArchive(
|
|
621
|
-
name=
|
|
626
|
+
name=formatting.name,
|
|
622
627
|
format_type_pre_bnc=FormatType.CUSTOM_NUMBER,
|
|
623
628
|
format_type=FormatType.CUSTOM_NUMBER,
|
|
624
629
|
default_format=TSKArchives.FormatStructArchive(
|
|
@@ -640,45 +645,49 @@ class _NumbersModel(Cacheable):
|
|
|
640
645
|
use_accounting_style=False,
|
|
641
646
|
),
|
|
642
647
|
)
|
|
643
|
-
self.add_custom_format_archive(
|
|
648
|
+
self.add_custom_format_archive(formatting, format_archive)
|
|
644
649
|
|
|
645
|
-
def add_custom_datetime_format_archive(self,
|
|
650
|
+
def add_custom_datetime_format_archive(self, formatting: CustomFormatting) -> None:
|
|
646
651
|
format_archive = TSKArchives.CustomFormatArchive(
|
|
647
|
-
name=
|
|
652
|
+
name=formatting.name,
|
|
648
653
|
format_type_pre_bnc=FormatType.CUSTOM_DATE,
|
|
649
654
|
format_type=FormatType.CUSTOM_DATE,
|
|
650
655
|
default_format=TSKArchives.FormatStructArchive(
|
|
651
|
-
custom_format_string=
|
|
656
|
+
custom_format_string=formatting.format,
|
|
652
657
|
format_type=FormatType.CUSTOM_DATE,
|
|
653
658
|
),
|
|
654
659
|
)
|
|
655
|
-
self.add_custom_format_archive(
|
|
660
|
+
self.add_custom_format_archive(formatting, format_archive)
|
|
656
661
|
|
|
657
|
-
def add_custom_format_archive(
|
|
662
|
+
def add_custom_format_archive(
|
|
663
|
+
self,
|
|
664
|
+
formatting: CustomFormatting,
|
|
665
|
+
format_archive: object,
|
|
666
|
+
) -> None:
|
|
658
667
|
format_uuid = NumbersUUID().protobuf2
|
|
659
|
-
self._custom_formats[
|
|
660
|
-
self._custom_format_archives[
|
|
661
|
-
self._custom_format_uuids[
|
|
668
|
+
self._custom_formats[formatting.name] = formatting
|
|
669
|
+
self._custom_format_archives[formatting.name] = format_archive
|
|
670
|
+
self._custom_format_uuids[formatting.name] = format_uuid
|
|
662
671
|
|
|
663
672
|
custom_format_list_id = self.objects[DOCUMENT_ID].super.custom_format_list.identifier
|
|
664
673
|
custom_format_list = self.objects[custom_format_list_id]
|
|
665
674
|
custom_format_list.custom_formats.append(format_archive)
|
|
666
675
|
custom_format_list.uuids.append(format_uuid)
|
|
667
676
|
|
|
668
|
-
def custom_format_id(self, table_id: int,
|
|
677
|
+
def custom_format_id(self, table_id: int, formatting: CustomFormatting) -> int:
|
|
669
678
|
"""Look up the custom format and return the format ID for the table."""
|
|
670
|
-
format_type = CUSTOM_FORMAT_TYPE_MAP[
|
|
671
|
-
format_uuid = self._custom_format_uuids[
|
|
679
|
+
format_type = CUSTOM_FORMAT_TYPE_MAP[formatting.type]
|
|
680
|
+
format_uuid = self._custom_format_uuids[formatting.name]
|
|
672
681
|
custom_format = TSKArchives.FormatStructArchive(
|
|
673
682
|
format_type=format_type,
|
|
674
683
|
custom_uid=TSPMessages.UUID(lower=format_uuid.lower, upper=format_uuid.upper),
|
|
675
684
|
)
|
|
676
685
|
return self._table_formats.lookup_key(table_id, custom_format)
|
|
677
686
|
|
|
678
|
-
def add_custom_text_format_archive(self,
|
|
679
|
-
format_string =
|
|
687
|
+
def add_custom_text_format_archive(self, formatting: CustomFormatting) -> None:
|
|
688
|
+
format_string = formatting.format.replace("%s", CUSTOM_TEXT_PLACEHOLDER)
|
|
680
689
|
format_archive = TSKArchives.CustomFormatArchive(
|
|
681
|
-
name=
|
|
690
|
+
name=formatting.name,
|
|
682
691
|
format_type_pre_bnc=FormatType.CUSTOM_TEXT,
|
|
683
692
|
format_type=FormatType.CUSTOM_TEXT,
|
|
684
693
|
default_format=TSKArchives.FormatStructArchive(
|
|
@@ -686,7 +695,7 @@ class _NumbersModel(Cacheable):
|
|
|
686
695
|
format_type=FormatType.CUSTOM_TEXT,
|
|
687
696
|
),
|
|
688
697
|
)
|
|
689
|
-
self.add_custom_format_archive(
|
|
698
|
+
self.add_custom_format_archive(formatting, format_archive)
|
|
690
699
|
|
|
691
700
|
@cache(num_args=2)
|
|
692
701
|
def table_style(self, table_id: int, key: int) -> str:
|
|
@@ -699,12 +708,13 @@ class _NumbersModel(Cacheable):
|
|
|
699
708
|
"""Return the string associated with a string ID for a particular table."""
|
|
700
709
|
return self._table_strings.lookup_value(table_id, key).string
|
|
701
710
|
|
|
702
|
-
def init_table_strings(self, table_id: int):
|
|
711
|
+
def init_table_strings(self, table_id: int) -> None:
|
|
703
712
|
"""Cache table strings reference and delete all existing keys/values."""
|
|
704
713
|
self._table_strings.init(table_id)
|
|
705
714
|
|
|
706
715
|
def table_string_key(self, table_id: int, value: str) -> int:
|
|
707
|
-
"""
|
|
716
|
+
"""
|
|
717
|
+
Return the key associated with a string for a particular table. If
|
|
708
718
|
the string is not in the strings table, allocate a new entry with the
|
|
709
719
|
next available key.
|
|
710
720
|
"""
|
|
@@ -712,7 +722,8 @@ class _NumbersModel(Cacheable):
|
|
|
712
722
|
|
|
713
723
|
@cache(num_args=0)
|
|
714
724
|
def owner_id_map(self):
|
|
715
|
-
"""
|
|
725
|
+
"""
|
|
726
|
+
"
|
|
716
727
|
Extracts the mapping table from Owner IDs to UUIDs. Returns a
|
|
717
728
|
dictionary mapping the owner ID int to a 128-bit UUID.
|
|
718
729
|
"""
|
|
@@ -789,8 +800,7 @@ class _NumbersModel(Cacheable):
|
|
|
789
800
|
ce_id = self.find_refs("CalculationEngineArchive")
|
|
790
801
|
if len(ce_id) == 0:
|
|
791
802
|
return 0
|
|
792
|
-
|
|
793
|
-
return ce_id[0]
|
|
803
|
+
return ce_id[0]
|
|
794
804
|
|
|
795
805
|
@cache(num_args=0)
|
|
796
806
|
def calc_engine(self):
|
|
@@ -798,11 +808,10 @@ class _NumbersModel(Cacheable):
|
|
|
798
808
|
ce_id = self.calc_engine_id()
|
|
799
809
|
if ce_id == 0:
|
|
800
810
|
return None
|
|
801
|
-
|
|
802
|
-
return self.objects[ce_id]
|
|
811
|
+
return self.objects[ce_id]
|
|
803
812
|
|
|
804
813
|
@cache()
|
|
805
|
-
def calculate_merge_cell_ranges(self, table_id):
|
|
814
|
+
def calculate_merge_cell_ranges(self, table_id) -> None:
|
|
806
815
|
"""Exract all the merge cell ranges for the Table."""
|
|
807
816
|
# https://github.com/masaccio/numbers-parser/blob/main/doc/Numbers.md#merge-ranges
|
|
808
817
|
owner_id_map = self.owner_id_map()
|
|
@@ -865,10 +874,11 @@ class _NumbersModel(Cacheable):
|
|
|
865
874
|
for sheet_id in self.sheet_ids(): # pragma: no branch
|
|
866
875
|
if table_id in self.table_ids(sheet_id):
|
|
867
876
|
return sheet_id
|
|
877
|
+
return None
|
|
868
878
|
|
|
869
879
|
@cache()
|
|
870
|
-
def table_uuids_to_id(self, table_uuid) -> int:
|
|
871
|
-
for sheet_id in self.sheet_ids(): # pragma: no branch
|
|
880
|
+
def table_uuids_to_id(self, table_uuid) -> int | None:
|
|
881
|
+
for sheet_id in self.sheet_ids(): # pragma: no branch # noqa: RET503
|
|
872
882
|
for table_id in self.table_ids(sheet_id):
|
|
873
883
|
if table_uuid == self.table_base_id(table_id):
|
|
874
884
|
return table_id
|
|
@@ -891,12 +901,11 @@ class _NumbersModel(Cacheable):
|
|
|
891
901
|
|
|
892
902
|
if node.HasField("AST_colon_tract"):
|
|
893
903
|
return self.tract_to_row_col_ref(node, other_table_name, row, col)
|
|
894
|
-
|
|
904
|
+
if node.HasField("AST_row") and not node.HasField("AST_column"):
|
|
895
905
|
return node_to_row_ref(node, other_table_name, row)
|
|
896
|
-
|
|
906
|
+
if node.HasField("AST_column") and not node.HasField("AST_row"):
|
|
897
907
|
return node_to_col_ref(node, other_table_name, col)
|
|
898
|
-
|
|
899
|
-
return node_to_row_col_ref(node, other_table_name, row, col)
|
|
908
|
+
return node_to_row_col_ref(node, other_table_name, row, col)
|
|
900
909
|
|
|
901
910
|
def tract_to_row_col_ref(self, node: object, table_name: str, row: int, col: int) -> str:
|
|
902
911
|
if node.AST_sticky_bits.begin_row_is_absolute:
|
|
@@ -933,8 +942,7 @@ class _NumbersModel(Cacheable):
|
|
|
933
942
|
)
|
|
934
943
|
if table_name is not None:
|
|
935
944
|
return f"{table_name}::{begin_ref}:{end_ref}"
|
|
936
|
-
|
|
937
|
-
return f"{begin_ref}:{end_ref}"
|
|
945
|
+
return f"{begin_ref}:{end_ref}"
|
|
938
946
|
|
|
939
947
|
@cache()
|
|
940
948
|
def formula_ast(self, table_id: int):
|
|
@@ -947,7 +955,7 @@ class _NumbersModel(Cacheable):
|
|
|
947
955
|
return formulas
|
|
948
956
|
|
|
949
957
|
@cache()
|
|
950
|
-
def storage_buffers(self, table_id: int) ->
|
|
958
|
+
def storage_buffers(self, table_id: int) -> list:
|
|
951
959
|
buffers = []
|
|
952
960
|
for tile in self.table_tiles(table_id):
|
|
953
961
|
if not tile.last_saved_in_BNC:
|
|
@@ -975,7 +983,7 @@ class _NumbersModel(Cacheable):
|
|
|
975
983
|
return None
|
|
976
984
|
return storage_buffers[row_offset][col]
|
|
977
985
|
|
|
978
|
-
def recalculate_row_headers(self, table_id: int, data:
|
|
986
|
+
def recalculate_row_headers(self, table_id: int, data: list) -> None:
|
|
979
987
|
base_data_store = self.objects[table_id].base_data_store
|
|
980
988
|
buckets = self.objects[base_data_store.rowHeaders.buckets[0].identifier]
|
|
981
989
|
clear_field_container(buckets.headers)
|
|
@@ -992,7 +1000,7 @@ class _NumbersModel(Cacheable):
|
|
|
992
1000
|
)
|
|
993
1001
|
buckets.headers.append(header)
|
|
994
1002
|
|
|
995
|
-
def recalculate_column_headers(self, table_id: int, data:
|
|
1003
|
+
def recalculate_column_headers(self, table_id: int, data: list) -> None:
|
|
996
1004
|
current_column_widths = {}
|
|
997
1005
|
for col in range(self.number_of_columns(table_id)):
|
|
998
1006
|
current_column_widths[col] = self.col_width(table_id, col)
|
|
@@ -1014,7 +1022,7 @@ class _NumbersModel(Cacheable):
|
|
|
1014
1022
|
)
|
|
1015
1023
|
buckets.headers.append(header)
|
|
1016
1024
|
|
|
1017
|
-
def recalculate_merged_cells(self, table_id: int):
|
|
1025
|
+
def recalculate_merged_cells(self, table_id: int) -> None:
|
|
1018
1026
|
merge_cells = self.merge_cells(table_id)
|
|
1019
1027
|
|
|
1020
1028
|
merge_map_id, merge_map = self.objects.create_object_from_dict(
|
|
@@ -1037,7 +1045,7 @@ class _NumbersModel(Cacheable):
|
|
|
1037
1045
|
def recalculate_row_info(
|
|
1038
1046
|
self,
|
|
1039
1047
|
table_id: int,
|
|
1040
|
-
data:
|
|
1048
|
+
data: list,
|
|
1041
1049
|
tile_row_offset: int,
|
|
1042
1050
|
row: int,
|
|
1043
1051
|
) -> TSTArchives.TileRowInfo:
|
|
@@ -1068,18 +1076,18 @@ class _NumbersModel(Cacheable):
|
|
|
1068
1076
|
return row_info
|
|
1069
1077
|
|
|
1070
1078
|
@cache()
|
|
1071
|
-
def metadata_component(self, reference:
|
|
1079
|
+
def metadata_component(self, reference: str | int | None = None) -> int:
|
|
1072
1080
|
"""Return the ID of an object in the document metadata given it's name or ID."""
|
|
1073
1081
|
component_map = {c.identifier: c for c in self.objects[PACKAGE_ID].components}
|
|
1074
1082
|
if isinstance(reference, str):
|
|
1075
1083
|
component_ids = [
|
|
1076
|
-
|
|
1084
|
+
x for x, c in component_map.items() if c.preferred_locator == reference
|
|
1077
1085
|
]
|
|
1078
1086
|
else:
|
|
1079
|
-
component_ids = [
|
|
1087
|
+
component_ids = [x for x, c in component_map.items() if c.identifier == reference]
|
|
1080
1088
|
return component_map[component_ids[0]]
|
|
1081
1089
|
|
|
1082
|
-
def add_component_metadata(self, object_id: int, parent: str, locator: str):
|
|
1090
|
+
def add_component_metadata(self, object_id: int, parent: str, locator: str) -> None:
|
|
1083
1091
|
"""Add a new ComponentInfo record to the parent object in the document metadata."""
|
|
1084
1092
|
locator = locator.format(object_id)
|
|
1085
1093
|
preferred_locator = re.sub(r"\-\d+.*", "", locator)
|
|
@@ -1098,10 +1106,10 @@ class _NumbersModel(Cacheable):
|
|
|
1098
1106
|
def add_component_reference(
|
|
1099
1107
|
self,
|
|
1100
1108
|
object_id: int,
|
|
1101
|
-
location:
|
|
1102
|
-
component_id:
|
|
1109
|
+
location: str | None = None,
|
|
1110
|
+
component_id: int | None = None,
|
|
1103
1111
|
is_weak: bool = False,
|
|
1104
|
-
):
|
|
1112
|
+
) -> None:
|
|
1105
1113
|
"""Add an external reference to an object in a metadata component."""
|
|
1106
1114
|
component = self.metadata_component(location or component_id)
|
|
1107
1115
|
if component_id is not None:
|
|
@@ -1114,7 +1122,7 @@ class _NumbersModel(Cacheable):
|
|
|
1114
1122
|
TSPArchiveMessages.ComponentExternalReference(**params),
|
|
1115
1123
|
)
|
|
1116
1124
|
|
|
1117
|
-
def recalculate_table_data(self, table_id: int, data:
|
|
1125
|
+
def recalculate_table_data(self, table_id: int, data: list) -> None:
|
|
1118
1126
|
table_model = self.objects[table_id]
|
|
1119
1127
|
table_model.number_of_rows = len(data)
|
|
1120
1128
|
table_model.number_of_columns = len(data[0])
|
|
@@ -1191,7 +1199,7 @@ class _NumbersModel(Cacheable):
|
|
|
1191
1199
|
height += self.row_height(table_id, row)
|
|
1192
1200
|
return floor(height)
|
|
1193
1201
|
|
|
1194
|
-
def row_height(self, table_id: int, row: int, height:
|
|
1202
|
+
def row_height(self, table_id: int, row: int, height: int | None = None) -> int:
|
|
1195
1203
|
if height is not None:
|
|
1196
1204
|
if table_id not in self._row_heights:
|
|
1197
1205
|
self._row_heights[table_id] = {}
|
|
@@ -1218,7 +1226,7 @@ class _NumbersModel(Cacheable):
|
|
|
1218
1226
|
data[row][col].border.top.width
|
|
1219
1227
|
for col in range(len(data[row]))
|
|
1220
1228
|
if data[row][col].border.top is not None
|
|
1221
|
-
]
|
|
1229
|
+
],
|
|
1222
1230
|
)
|
|
1223
1231
|
max_bottom_border = max(
|
|
1224
1232
|
[0.0]
|
|
@@ -1226,7 +1234,7 @@ class _NumbersModel(Cacheable):
|
|
|
1226
1234
|
data[row][col].border.bottom.width
|
|
1227
1235
|
for col in range(len(data[row]))
|
|
1228
1236
|
if data[row][col].border.bottom is not None
|
|
1229
|
-
]
|
|
1237
|
+
],
|
|
1230
1238
|
)
|
|
1231
1239
|
height += max_top_border / 2
|
|
1232
1240
|
height += max_bottom_border / 2
|
|
@@ -1243,7 +1251,7 @@ class _NumbersModel(Cacheable):
|
|
|
1243
1251
|
width += self.col_width(table_id, row)
|
|
1244
1252
|
return round(width)
|
|
1245
1253
|
|
|
1246
|
-
def col_width(self, table_id: int, col: int, width:
|
|
1254
|
+
def col_width(self, table_id: int, col: int, width: int | None = None) -> int:
|
|
1247
1255
|
if width is not None:
|
|
1248
1256
|
if table_id not in self._col_widths:
|
|
1249
1257
|
self._col_widths[table_id] = {}
|
|
@@ -1270,7 +1278,7 @@ class _NumbersModel(Cacheable):
|
|
|
1270
1278
|
data[row][col].border.left.width
|
|
1271
1279
|
for row in range(len(data))
|
|
1272
1280
|
if data[row][col].border.left is not None
|
|
1273
|
-
]
|
|
1281
|
+
],
|
|
1274
1282
|
)
|
|
1275
1283
|
max_right_border = max(
|
|
1276
1284
|
[0.0]
|
|
@@ -1278,7 +1286,7 @@ class _NumbersModel(Cacheable):
|
|
|
1278
1286
|
data[row][col].border.right.width
|
|
1279
1287
|
for row in range(len(data))
|
|
1280
1288
|
if data[row][col].border.right is not None
|
|
1281
|
-
]
|
|
1289
|
+
],
|
|
1282
1290
|
)
|
|
1283
1291
|
width += max_left_border / 2
|
|
1284
1292
|
width += max_right_border / 2
|
|
@@ -1288,21 +1296,21 @@ class _NumbersModel(Cacheable):
|
|
|
1288
1296
|
self._col_widths[table_id][col] = floor(width)
|
|
1289
1297
|
return self._col_widths[table_id][col]
|
|
1290
1298
|
|
|
1291
|
-
def num_header_rows(self, table_id: int, num_headers:
|
|
1299
|
+
def num_header_rows(self, table_id: int, num_headers: int | None = None) -> int:
|
|
1292
1300
|
"""Return/set the number of header rows."""
|
|
1293
1301
|
table_model = self.objects[table_id]
|
|
1294
1302
|
if num_headers is not None:
|
|
1295
1303
|
table_model.number_of_header_rows = num_headers
|
|
1296
1304
|
return table_model.number_of_header_rows
|
|
1297
1305
|
|
|
1298
|
-
def num_header_cols(self, table_id: int, num_headers:
|
|
1306
|
+
def num_header_cols(self, table_id: int, num_headers: int | None = None) -> int:
|
|
1299
1307
|
"""Return/set the number of header columns."""
|
|
1300
1308
|
table_model = self.objects[table_id]
|
|
1301
1309
|
if num_headers is not None:
|
|
1302
1310
|
table_model.number_of_header_columns = num_headers
|
|
1303
1311
|
return table_model.number_of_header_columns
|
|
1304
1312
|
|
|
1305
|
-
def table_coordinates(self, table_id: int) ->
|
|
1313
|
+
def table_coordinates(self, table_id: int) -> tuple[float]:
|
|
1306
1314
|
table_info = self.objects[self.table_info_id(table_id)]
|
|
1307
1315
|
return (
|
|
1308
1316
|
table_info.super.geometry.position.x,
|
|
@@ -1347,7 +1355,7 @@ class _NumbersModel(Cacheable):
|
|
|
1347
1355
|
),
|
|
1348
1356
|
)
|
|
1349
1357
|
|
|
1350
|
-
def add_table(
|
|
1358
|
+
def add_table(
|
|
1351
1359
|
self,
|
|
1352
1360
|
sheet_id: int,
|
|
1353
1361
|
table_name: str,
|
|
@@ -1484,7 +1492,9 @@ class _NumbersModel(Cacheable):
|
|
|
1484
1492
|
table_info.super.MergeFrom(self.create_drawable(sheet_id, x, y))
|
|
1485
1493
|
|
|
1486
1494
|
self.add_component_reference(
|
|
1487
|
-
table_info_id,
|
|
1495
|
+
table_info_id,
|
|
1496
|
+
location="Document",
|
|
1497
|
+
component_id=self.calc_engine_id(),
|
|
1488
1498
|
)
|
|
1489
1499
|
self.create_caption_archive(table_model_id)
|
|
1490
1500
|
self.caption_enabled(table_model_id, False)
|
|
@@ -1502,15 +1512,16 @@ class _NumbersModel(Cacheable):
|
|
|
1502
1512
|
)
|
|
1503
1513
|
return table_model_id
|
|
1504
1514
|
|
|
1505
|
-
def add_formula_owner(
|
|
1515
|
+
def add_formula_owner(
|
|
1506
1516
|
self,
|
|
1507
1517
|
table_info_id: int,
|
|
1508
1518
|
num_rows: int,
|
|
1509
1519
|
num_cols: int,
|
|
1510
1520
|
number_of_header_rows: int,
|
|
1511
1521
|
number_of_header_columns: int,
|
|
1512
|
-
):
|
|
1513
|
-
"""
|
|
1522
|
+
) -> None:
|
|
1523
|
+
"""
|
|
1524
|
+
Create a FormulaOwnerDependenciesArchive that references a TableInfoArchive
|
|
1514
1525
|
so that cross-references to cells in this table will work.
|
|
1515
1526
|
"""
|
|
1516
1527
|
formula_owner_uuid = NumbersUUID()
|
|
@@ -1589,7 +1600,10 @@ class _NumbersModel(Cacheable):
|
|
|
1589
1600
|
)
|
|
1590
1601
|
|
|
1591
1602
|
self.add_component_reference(
|
|
1592
|
-
sheet_id,
|
|
1603
|
+
sheet_id,
|
|
1604
|
+
location="CalculationEngine",
|
|
1605
|
+
component_id=DOCUMENT_ID,
|
|
1606
|
+
is_weak=True,
|
|
1593
1607
|
)
|
|
1594
1608
|
|
|
1595
1609
|
self.objects[DOCUMENT_ID].sheets.append(TSPMessages.Reference(identifier=sheet_id))
|
|
@@ -1603,7 +1617,7 @@ class _NumbersModel(Cacheable):
|
|
|
1603
1617
|
return self._styles
|
|
1604
1618
|
|
|
1605
1619
|
@cache(num_args=0)
|
|
1606
|
-
def available_paragraph_styles(self) ->
|
|
1620
|
+
def available_paragraph_styles(self) -> dict[str, Style]:
|
|
1607
1621
|
theme_id = self.objects[DOCUMENT_ID].theme.identifier
|
|
1608
1622
|
presets = find_extension(self.objects[theme_id].super, "paragraph_style_presets")
|
|
1609
1623
|
presets_map = {
|
|
@@ -1707,7 +1721,7 @@ class _NumbersModel(Cacheable):
|
|
|
1707
1721
|
self._styles[style.name] = style
|
|
1708
1722
|
return para_style_id
|
|
1709
1723
|
|
|
1710
|
-
def update_paragraph_style(self, style: Style):
|
|
1724
|
+
def update_paragraph_style(self, style: Style) -> None:
|
|
1711
1725
|
if style.underline:
|
|
1712
1726
|
underline = CharacterStyle.UnderlineType.kSingleUnderline
|
|
1713
1727
|
else:
|
|
@@ -1734,8 +1748,9 @@ class _NumbersModel(Cacheable):
|
|
|
1734
1748
|
style_obj.para_properties.left_indent = style.left_indent
|
|
1735
1749
|
style_obj.para_properties.right_indent = style.right_indent
|
|
1736
1750
|
|
|
1737
|
-
def update_paragraph_styles(self):
|
|
1738
|
-
"""
|
|
1751
|
+
def update_paragraph_styles(self) -> None:
|
|
1752
|
+
"""
|
|
1753
|
+
Create new paragraph style archives for any new styles that
|
|
1739
1754
|
have been created for this document.
|
|
1740
1755
|
"""
|
|
1741
1756
|
new_styles = [x for x in self.styles.values() if x._text_style_obj_id is None]
|
|
@@ -1751,8 +1766,9 @@ class _NumbersModel(Cacheable):
|
|
|
1751
1766
|
for style in updated_styles:
|
|
1752
1767
|
self.update_paragraph_style(style)
|
|
1753
1768
|
|
|
1754
|
-
def update_cell_styles(self, table_id: int, data:
|
|
1755
|
-
"""
|
|
1769
|
+
def update_cell_styles(self, table_id: int, data: list) -> None:
|
|
1770
|
+
"""
|
|
1771
|
+
Create new cell style archives for any cells whose styles
|
|
1756
1772
|
have changes that require a cell style.
|
|
1757
1773
|
"""
|
|
1758
1774
|
cell_styles = {}
|
|
@@ -1781,7 +1797,7 @@ class _NumbersModel(Cacheable):
|
|
|
1781
1797
|
|
|
1782
1798
|
def add_cell_style(self, style: Style) -> int:
|
|
1783
1799
|
if style.bg_image is not None:
|
|
1784
|
-
digest = sha1(style.bg_image.data).digest()
|
|
1800
|
+
digest = sha1(style.bg_image.data).digest() # noqa: S324
|
|
1785
1801
|
if digest in self._images:
|
|
1786
1802
|
image_id = self._images[digest]
|
|
1787
1803
|
else:
|
|
@@ -1871,7 +1887,8 @@ class _NumbersModel(Cacheable):
|
|
|
1871
1887
|
return entry.reference.identifier
|
|
1872
1888
|
|
|
1873
1889
|
def custom_style_name(self) -> str:
|
|
1874
|
-
"""
|
|
1890
|
+
"""
|
|
1891
|
+
Find custom styles in the current document and return the next
|
|
1875
1892
|
highest numbered style.
|
|
1876
1893
|
"""
|
|
1877
1894
|
stylesheet_id = self.objects[DOCUMENT_ID].stylesheet.identifier
|
|
@@ -1887,11 +1904,10 @@ class _NumbersModel(Cacheable):
|
|
|
1887
1904
|
offset = len("Custom Style ")
|
|
1888
1905
|
custom_style_ids = [int(x[offset:]) for x in custom_styles]
|
|
1889
1906
|
return "Custom Style " + str(custom_style_ids[-1] + 1)
|
|
1890
|
-
|
|
1891
|
-
return "Custom Style 1"
|
|
1907
|
+
return "Custom Style 1"
|
|
1892
1908
|
|
|
1893
1909
|
@property
|
|
1894
|
-
def custom_formats(self) ->
|
|
1910
|
+
def custom_formats(self) -> dict[str, CustomFormatting]:
|
|
1895
1911
|
if self._custom_formats is None:
|
|
1896
1912
|
custom_format_list_id = self.objects[DOCUMENT_ID].super.custom_format_list.identifier
|
|
1897
1913
|
custom_formats = self.objects[custom_format_list_id].custom_formats
|
|
@@ -1908,7 +1924,8 @@ class _NumbersModel(Cacheable):
|
|
|
1908
1924
|
return self._custom_formats
|
|
1909
1925
|
|
|
1910
1926
|
def custom_format_name(self) -> str:
|
|
1911
|
-
"""
|
|
1927
|
+
"""
|
|
1928
|
+
Find custom formats in the current document and return the next
|
|
1912
1929
|
highest numbered format.
|
|
1913
1930
|
"""
|
|
1914
1931
|
current_formats = self.custom_formats.keys()
|
|
@@ -1920,15 +1937,14 @@ class _NumbersModel(Cacheable):
|
|
|
1920
1937
|
if len(current_formats) > 0:
|
|
1921
1938
|
last_id = int(current_formats[-1])
|
|
1922
1939
|
return f"Custom Format {last_id + 1}"
|
|
1923
|
-
|
|
1924
|
-
return "Custom Format 1"
|
|
1940
|
+
return "Custom Format 1"
|
|
1925
1941
|
|
|
1926
1942
|
@cache()
|
|
1927
1943
|
def table_formulas(self, table_id: int):
|
|
1928
1944
|
return TableFormulas(self, table_id)
|
|
1929
1945
|
|
|
1930
1946
|
@cache(num_args=2)
|
|
1931
|
-
def table_rich_text(self, table_id: int, string_key: int) ->
|
|
1947
|
+
def table_rich_text(self, table_id: int, string_key: int) -> dict:
|
|
1932
1948
|
"""Extract bullets and hyperlinks from a rich text data cell."""
|
|
1933
1949
|
# The table model base data store contains a richTextTable field
|
|
1934
1950
|
# which is a reference to a TST.TableDataList. The TableDataList
|
|
@@ -1963,7 +1979,7 @@ class _NumbersModel(Cacheable):
|
|
|
1963
1979
|
# a string with a new bullet character
|
|
1964
1980
|
bds = self.objects[table_id].base_data_store
|
|
1965
1981
|
rich_text_table = self.objects[bds.rich_text_table.identifier]
|
|
1966
|
-
for entry in rich_text_table.entries: # pragma: no branch
|
|
1982
|
+
for entry in rich_text_table.entries: # pragma: no branch # noqa: RET503
|
|
1967
1983
|
if string_key == entry.key:
|
|
1968
1984
|
payload = self.objects[entry.rich_text_payload.identifier]
|
|
1969
1985
|
payload_storage = self.objects[payload.storage.identifier]
|
|
@@ -2019,7 +2035,8 @@ class _NumbersModel(Cacheable):
|
|
|
2019
2035
|
}
|
|
2020
2036
|
|
|
2021
2037
|
def cell_text_style(self, cell: Cell) -> object:
|
|
2022
|
-
"""
|
|
2038
|
+
"""
|
|
2039
|
+
Return the text style object for the cell or, if none
|
|
2023
2040
|
is defined, the default header, footer or body style.
|
|
2024
2041
|
"""
|
|
2025
2042
|
if cell._text_style_id is not None:
|
|
@@ -2028,9 +2045,9 @@ class _NumbersModel(Cacheable):
|
|
|
2028
2045
|
table_model = self.objects[cell._table_id]
|
|
2029
2046
|
if cell.row in range(table_model.number_of_header_rows):
|
|
2030
2047
|
return self.objects[table_model.header_row_text_style.identifier]
|
|
2031
|
-
|
|
2048
|
+
if cell.col in range(table_model.number_of_header_columns):
|
|
2032
2049
|
return self.objects[table_model.header_column_text_style.identifier]
|
|
2033
|
-
|
|
2050
|
+
if table_model.number_of_footer_rows > 0:
|
|
2034
2051
|
start_row_num = table_model.number_of_rows - table_model.number_of_footer_rows
|
|
2035
2052
|
end_row_num = start_row_num + table_model.number_of_footer_rows
|
|
2036
2053
|
if cell.row in range(start_row_num, end_row_num):
|
|
@@ -2048,7 +2065,7 @@ class _NumbersModel(Cacheable):
|
|
|
2048
2065
|
vertical = VerticalJustification(self.cell_property(style, "vertical_alignment"))
|
|
2049
2066
|
return Alignment(horizontal, vertical)
|
|
2050
2067
|
|
|
2051
|
-
def cell_bg_color(self, cell: Cell) ->
|
|
2068
|
+
def cell_bg_color(self, cell: Cell) -> tuple | list[tuple]:
|
|
2052
2069
|
if cell._cell_style_id is None:
|
|
2053
2070
|
return None
|
|
2054
2071
|
|
|
@@ -2057,115 +2074,111 @@ class _NumbersModel(Cacheable):
|
|
|
2057
2074
|
|
|
2058
2075
|
if cell_properties.HasField("color"):
|
|
2059
2076
|
return rgb(cell_properties.color)
|
|
2060
|
-
|
|
2077
|
+
if cell_properties.HasField("gradient"):
|
|
2061
2078
|
return [(rgb(s.color)) for s in cell_properties.gradient.stops]
|
|
2062
2079
|
return None
|
|
2063
2080
|
|
|
2064
2081
|
def char_property(self, style: object, field: str):
|
|
2065
|
-
"""
|
|
2082
|
+
"""
|
|
2083
|
+
Return a char_property field from a style if present
|
|
2066
2084
|
in the style, or from the parent if not.
|
|
2067
2085
|
"""
|
|
2068
2086
|
if not style.char_properties.HasField(field):
|
|
2069
2087
|
parent = self.objects[style.super.parent.identifier]
|
|
2070
2088
|
return getattr(parent.char_properties, field)
|
|
2071
|
-
|
|
2072
|
-
return getattr(style.char_properties, field)
|
|
2089
|
+
return getattr(style.char_properties, field)
|
|
2073
2090
|
|
|
2074
2091
|
def para_property(self, style: object, field: str) -> float:
|
|
2075
|
-
"""
|
|
2092
|
+
"""
|
|
2093
|
+
Return a para_property field from a style if present
|
|
2076
2094
|
in the style, or from the parent if not.
|
|
2077
2095
|
"""
|
|
2078
2096
|
if not style.para_properties.HasField(field):
|
|
2079
2097
|
parent = self.objects[style.super.parent.identifier]
|
|
2080
2098
|
return getattr(parent.para_properties, field)
|
|
2081
|
-
|
|
2082
|
-
return getattr(style.para_properties, field)
|
|
2099
|
+
return getattr(style.para_properties, field)
|
|
2083
2100
|
|
|
2084
2101
|
def cell_property(self, style: object, field: str) -> float:
|
|
2085
|
-
"""
|
|
2102
|
+
"""
|
|
2103
|
+
Return a cell_property field from a style if present
|
|
2086
2104
|
in the style, or from the parent if not.
|
|
2087
2105
|
"""
|
|
2088
2106
|
if not style.cell_properties.HasField(field):
|
|
2089
2107
|
parent = self.objects[style.super.parent.identifier]
|
|
2090
2108
|
return getattr(parent.cell_properties, field)
|
|
2091
|
-
|
|
2092
|
-
return getattr(style.cell_properties, field)
|
|
2109
|
+
return getattr(style.cell_properties, field)
|
|
2093
2110
|
|
|
2094
|
-
def cell_is_bold(self, obj:
|
|
2111
|
+
def cell_is_bold(self, obj: Cell | object) -> bool:
|
|
2095
2112
|
style = self.cell_text_style(obj) if isinstance(obj, Cell) else obj
|
|
2096
2113
|
return self.char_property(style, "bold")
|
|
2097
2114
|
|
|
2098
|
-
def cell_is_italic(self, obj:
|
|
2115
|
+
def cell_is_italic(self, obj: Cell | object) -> bool:
|
|
2099
2116
|
style = self.cell_text_style(obj) if isinstance(obj, Cell) else obj
|
|
2100
2117
|
return self.char_property(style, "italic")
|
|
2101
2118
|
|
|
2102
|
-
def cell_is_underline(self, obj:
|
|
2119
|
+
def cell_is_underline(self, obj: Cell | object) -> bool:
|
|
2103
2120
|
style = self.cell_text_style(obj) if isinstance(obj, Cell) else obj
|
|
2104
2121
|
underline = self.char_property(style, "underline")
|
|
2105
2122
|
return underline != CharacterStyle.UnderlineType.kNoUnderline
|
|
2106
2123
|
|
|
2107
|
-
def cell_is_strikethrough(self, obj:
|
|
2124
|
+
def cell_is_strikethrough(self, obj: Cell | object) -> bool:
|
|
2108
2125
|
style = self.cell_text_style(obj) if isinstance(obj, Cell) else obj
|
|
2109
2126
|
strikethru = self.char_property(style, "strikethru")
|
|
2110
2127
|
return strikethru != CharacterStyle.StrikethruType.kNoStrikethru
|
|
2111
2128
|
|
|
2112
|
-
def cell_style_name(self, obj:
|
|
2129
|
+
def cell_style_name(self, obj: Cell | object) -> bool:
|
|
2113
2130
|
style = self.cell_text_style(obj) if isinstance(obj, Cell) else obj
|
|
2114
2131
|
return style.super.name
|
|
2115
2132
|
|
|
2116
|
-
def cell_font_color(self, obj:
|
|
2133
|
+
def cell_font_color(self, obj: Cell | object) -> tuple:
|
|
2117
2134
|
style = self.cell_text_style(obj) if isinstance(obj, Cell) else obj
|
|
2118
2135
|
return rgb(self.char_property(style, "font_color"))
|
|
2119
2136
|
|
|
2120
|
-
def cell_font_size(self, obj:
|
|
2137
|
+
def cell_font_size(self, obj: Cell | object) -> float:
|
|
2121
2138
|
style = self.cell_text_style(obj) if isinstance(obj, Cell) else obj
|
|
2122
2139
|
return self.char_property(style, "font_size")
|
|
2123
2140
|
|
|
2124
|
-
def cell_font_name(self, obj:
|
|
2141
|
+
def cell_font_name(self, obj: Cell | object) -> str:
|
|
2125
2142
|
style = self.cell_text_style(obj) if isinstance(obj, Cell) else obj
|
|
2126
2143
|
font_name = self.char_property(style, "font_name")
|
|
2127
2144
|
return FONT_NAME_TO_FAMILY[font_name]
|
|
2128
2145
|
|
|
2129
|
-
def cell_first_indent(self, obj:
|
|
2146
|
+
def cell_first_indent(self, obj: Cell | object) -> float:
|
|
2130
2147
|
style = self.cell_text_style(obj) if isinstance(obj, Cell) else obj
|
|
2131
2148
|
return self.para_property(style, "first_line_indent")
|
|
2132
2149
|
|
|
2133
|
-
def cell_left_indent(self, obj:
|
|
2150
|
+
def cell_left_indent(self, obj: Cell | object) -> float:
|
|
2134
2151
|
style = self.cell_text_style(obj) if isinstance(obj, Cell) else obj
|
|
2135
2152
|
return self.para_property(style, "left_indent")
|
|
2136
2153
|
|
|
2137
|
-
def cell_right_indent(self, obj:
|
|
2154
|
+
def cell_right_indent(self, obj: Cell | object) -> float:
|
|
2138
2155
|
style = self.cell_text_style(obj) if isinstance(obj, Cell) else obj
|
|
2139
2156
|
return self.para_property(style, "right_indent")
|
|
2140
2157
|
|
|
2141
2158
|
def cell_text_inset(self, cell: Cell) -> float:
|
|
2142
2159
|
if cell._cell_style_id is None:
|
|
2143
2160
|
return DEFAULT_TEXT_INSET
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
return padding.left
|
|
2161
|
+
style = self.table_style(cell._table_id, cell._cell_style_id)
|
|
2162
|
+
padding = self.cell_property(style, "padding")
|
|
2163
|
+
# Padding is always identical (only one UI setting)
|
|
2164
|
+
return padding.left
|
|
2149
2165
|
|
|
2150
2166
|
def cell_text_wrap(self, cell: Cell) -> float:
|
|
2151
2167
|
if cell._cell_style_id is None:
|
|
2152
2168
|
return DEFAULT_TEXT_WRAP
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
return self.cell_property(style, "text_wrap")
|
|
2169
|
+
style = self.table_style(cell._table_id, cell._cell_style_id)
|
|
2170
|
+
return self.cell_property(style, "text_wrap")
|
|
2156
2171
|
|
|
2157
2172
|
def stroke_type(self, stroke_run: object) -> str:
|
|
2158
2173
|
"""Return the stroke type for a stroke run."""
|
|
2159
2174
|
stroke_type = stroke_run.stroke.pattern.type
|
|
2160
2175
|
if stroke_type == StrokePattern.StrokePatternType.TSDSolidPattern:
|
|
2161
2176
|
return "solid"
|
|
2162
|
-
|
|
2177
|
+
if stroke_type == StrokePattern.StrokePatternType.TSDPattern:
|
|
2163
2178
|
if stroke_run.stroke.pattern.pattern[0] < 1.0:
|
|
2164
2179
|
return "dots"
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
else:
|
|
2168
|
-
return "none"
|
|
2180
|
+
return "dashes"
|
|
2181
|
+
return "none"
|
|
2169
2182
|
|
|
2170
2183
|
def cell_for_stroke(self, table_id: int, side: str, row: int, col: int) -> object:
|
|
2171
2184
|
data = self._table_data[table_id]
|
|
@@ -2189,14 +2202,14 @@ class _NumbersModel(Cacheable):
|
|
|
2189
2202
|
return cell
|
|
2190
2203
|
return None
|
|
2191
2204
|
|
|
2192
|
-
def set_cell_border(
|
|
2205
|
+
def set_cell_border(
|
|
2193
2206
|
self,
|
|
2194
2207
|
table_id: int,
|
|
2195
2208
|
row: int,
|
|
2196
2209
|
col: int,
|
|
2197
2210
|
side: str,
|
|
2198
2211
|
border_value: Border,
|
|
2199
|
-
):
|
|
2212
|
+
) -> None:
|
|
2200
2213
|
"""Set the 2 borders adjacent to a stroke if within the table range."""
|
|
2201
2214
|
if side == "top":
|
|
2202
2215
|
if (cell := self.cell_for_stroke(table_id, "top", row, col)) is not None:
|
|
@@ -2231,7 +2244,7 @@ class _NumbersModel(Cacheable):
|
|
|
2231
2244
|
self._col_widths[table_id].pop(col, None)
|
|
2232
2245
|
self._col_widths[table_id].pop(col - 1, None)
|
|
2233
2246
|
|
|
2234
|
-
def extract_strokes_in_layers(self, table_id: int, layer_ids:
|
|
2247
|
+
def extract_strokes_in_layers(self, table_id: int, layer_ids: list, side: str) -> None:
|
|
2235
2248
|
for layer_id in layer_ids:
|
|
2236
2249
|
stroke_layer = self.objects[layer_id.identifier]
|
|
2237
2250
|
for stroke_run in stroke_layer.stroke_runs:
|
|
@@ -2253,7 +2266,7 @@ class _NumbersModel(Cacheable):
|
|
|
2253
2266
|
self.set_cell_border(table_id, row, start_column, side, border_value)
|
|
2254
2267
|
|
|
2255
2268
|
@cache()
|
|
2256
|
-
def extract_strokes(self, table_id: int):
|
|
2269
|
+
def extract_strokes(self, table_id: int) -> None:
|
|
2257
2270
|
table_obj = self.objects[table_id]
|
|
2258
2271
|
sidecar_obj = self.objects[table_obj.stroke_sidecar.identifier]
|
|
2259
2272
|
self.extract_strokes_in_layers(table_id, sidecar_obj.top_row_stroke_layers, "top")
|
|
@@ -2318,7 +2331,7 @@ class _NumbersModel(Cacheable):
|
|
|
2318
2331
|
),
|
|
2319
2332
|
)
|
|
2320
2333
|
|
|
2321
|
-
def add_stroke(
|
|
2334
|
+
def add_stroke(
|
|
2322
2335
|
self,
|
|
2323
2336
|
table_id: int,
|
|
2324
2337
|
row: int,
|
|
@@ -2326,7 +2339,7 @@ class _NumbersModel(Cacheable):
|
|
|
2326
2339
|
side: str,
|
|
2327
2340
|
border_value: Border,
|
|
2328
2341
|
length: int,
|
|
2329
|
-
):
|
|
2342
|
+
) -> None:
|
|
2330
2343
|
table_obj = self.objects[table_id]
|
|
2331
2344
|
sidecar_obj = self.objects[table_obj.stroke_sidecar.identifier]
|
|
2332
2345
|
sidecar_obj.max_order += 1
|
|
@@ -2420,8 +2433,7 @@ def range_end(obj):
|
|
|
2420
2433
|
"""Select end range for a IndexSetArchive.IndexSetEntry."""
|
|
2421
2434
|
if obj.HasField("range_end"):
|
|
2422
2435
|
return obj.range_end
|
|
2423
|
-
|
|
2424
|
-
return obj.range_begin
|
|
2436
|
+
return obj.range_begin
|
|
2425
2437
|
|
|
2426
2438
|
|
|
2427
2439
|
def formatted_number(number_type, index):
|
|
@@ -2439,8 +2451,7 @@ def node_to_col_ref(node: object, table_name: str, col: int) -> str:
|
|
|
2439
2451
|
col_name = xl_col_to_name(col, node.AST_column.absolute)
|
|
2440
2452
|
if table_name is not None:
|
|
2441
2453
|
return f"{table_name}::{col_name}"
|
|
2442
|
-
|
|
2443
|
-
return col_name
|
|
2454
|
+
return col_name
|
|
2444
2455
|
|
|
2445
2456
|
|
|
2446
2457
|
def node_to_row_ref(node: object, table_name: str, row: int) -> str:
|
|
@@ -2449,8 +2460,7 @@ def node_to_row_ref(node: object, table_name: str, row: int) -> str:
|
|
|
2449
2460
|
row_name = f"${row+1}" if node.AST_row.absolute else f"{row+1}"
|
|
2450
2461
|
if table_name is not None:
|
|
2451
2462
|
return f"{table_name}::{row_name}:{row_name}"
|
|
2452
|
-
|
|
2453
|
-
return f"{row_name}:{row_name}"
|
|
2463
|
+
return f"{row_name}:{row_name}"
|
|
2454
2464
|
|
|
2455
2465
|
|
|
2456
2466
|
def node_to_row_col_ref(node: object, table_name: str, row: int, col: int) -> str:
|
|
@@ -2465,8 +2475,7 @@ def node_to_row_col_ref(node: object, table_name: str, row: int, col: int) -> st
|
|
|
2465
2475
|
)
|
|
2466
2476
|
if table_name is not None:
|
|
2467
2477
|
return f"{table_name}::{ref}"
|
|
2468
|
-
|
|
2469
|
-
return ref
|
|
2478
|
+
return ref
|
|
2470
2479
|
|
|
2471
2480
|
|
|
2472
2481
|
def get_storage_buffers_for_row(
|
|
@@ -2474,8 +2483,9 @@ def get_storage_buffers_for_row(
|
|
|
2474
2483
|
offsets: list,
|
|
2475
2484
|
num_cols: int,
|
|
2476
2485
|
has_wide_offsets: bool,
|
|
2477
|
-
) ->
|
|
2478
|
-
"""
|
|
2486
|
+
) -> list[bytes]:
|
|
2487
|
+
"""
|
|
2488
|
+
Extract storage buffers for each cell in a table row.
|
|
2479
2489
|
|
|
2480
2490
|
Args:
|
|
2481
2491
|
----
|
|
@@ -2487,6 +2497,7 @@ def get_storage_buffers_for_row(
|
|
|
2487
2497
|
Returns:
|
|
2488
2498
|
-------
|
|
2489
2499
|
data: list of bytes for each cell in a row, or None if empty
|
|
2500
|
+
|
|
2490
2501
|
"""
|
|
2491
2502
|
offsets = array("h", offsets).tolist()
|
|
2492
2503
|
if has_wide_offsets:
|
|
@@ -2518,8 +2529,9 @@ def get_storage_buffers_for_row(
|
|
|
2518
2529
|
return data
|
|
2519
2530
|
|
|
2520
2531
|
|
|
2521
|
-
def clear_field_container(obj):
|
|
2522
|
-
"""
|
|
2532
|
+
def clear_field_container(obj) -> None:
|
|
2533
|
+
"""
|
|
2534
|
+
Remove all entries from a protobuf RepeatedCompositeFieldContainer
|
|
2523
2535
|
in a portable fashion.
|
|
2524
2536
|
"""
|
|
2525
2537
|
while len(obj) > 0:
|
|
@@ -2531,5 +2543,5 @@ def field_references(obj: object) -> dict:
|
|
|
2531
2543
|
return {
|
|
2532
2544
|
x[0].name: {"identifier": getattr(obj, x[0].name).identifier}
|
|
2533
2545
|
for x in obj.ListFields()
|
|
2534
|
-
if
|
|
2546
|
+
if isinstance(getattr(obj, x[0].name), TSPMessages.Reference)
|
|
2535
2547
|
}
|