numbers-parser 4.7.1__py3-none-any.whl → 4.8.0__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 +2 -1
- numbers_parser/cell.py +385 -100
- numbers_parser/cell_storage.py +151 -162
- numbers_parser/constants.py +165 -1
- numbers_parser/document.py +932 -228
- numbers_parser/formula.py +10 -10
- numbers_parser/model.py +291 -124
- numbers_parser-4.8.0.dist-info/METADATA +378 -0
- {numbers_parser-4.7.1.dist-info → numbers_parser-4.8.0.dist-info}/RECORD +12 -12
- {numbers_parser-4.7.1.dist-info → numbers_parser-4.8.0.dist-info}/WHEEL +1 -1
- numbers_parser-4.7.1.dist-info/METADATA +0 -626
- {numbers_parser-4.7.1.dist-info → numbers_parser-4.8.0.dist-info}/LICENSE.rst +0 -0
- {numbers_parser-4.7.1.dist-info → numbers_parser-4.8.0.dist-info}/entry_points.txt +0 -0
numbers_parser/model.py
CHANGED
|
@@ -17,14 +17,18 @@ from numbers_parser.cell import (
|
|
|
17
17
|
BoolCell,
|
|
18
18
|
Border,
|
|
19
19
|
BorderType,
|
|
20
|
+
CustomFormatting,
|
|
20
21
|
DateCell,
|
|
21
22
|
DurationCell,
|
|
22
23
|
EmptyCell,
|
|
24
|
+
Formatting,
|
|
25
|
+
FormattingType,
|
|
23
26
|
HorizontalJustification,
|
|
24
27
|
MergeAnchor,
|
|
25
28
|
MergedCell,
|
|
26
29
|
MergeReference,
|
|
27
30
|
NumberCell,
|
|
31
|
+
PaddingType,
|
|
28
32
|
RichTextCell,
|
|
29
33
|
Style,
|
|
30
34
|
TextCell,
|
|
@@ -35,7 +39,10 @@ from numbers_parser.cell import (
|
|
|
35
39
|
)
|
|
36
40
|
from numbers_parser.cell_storage import CellStorage
|
|
37
41
|
from numbers_parser.constants import (
|
|
42
|
+
ALLOWED_FORMATTING_PARAMETERS,
|
|
38
43
|
CURRENCY_CELL_TYPE,
|
|
44
|
+
CUSTOM_FORMAT_TYPE_MAP,
|
|
45
|
+
CUSTOM_TEXT_PLACEHOLDER,
|
|
39
46
|
DEFAULT_COLUMN_WIDTH,
|
|
40
47
|
DEFAULT_DOCUMENT,
|
|
41
48
|
DEFAULT_PRE_BNC_BYTES,
|
|
@@ -46,8 +53,10 @@ from numbers_parser.constants import (
|
|
|
46
53
|
DEFAULT_TILE_SIZE,
|
|
47
54
|
DOCUMENT_ID,
|
|
48
55
|
EPOCH,
|
|
56
|
+
FORMAT_TYPE_MAP,
|
|
49
57
|
MAX_TILE_SIZE,
|
|
50
58
|
PACKAGE_ID,
|
|
59
|
+
FormatType,
|
|
51
60
|
)
|
|
52
61
|
from numbers_parser.containers import ObjectStore
|
|
53
62
|
from numbers_parser.exceptions import UnsupportedError, UnsupportedWarning
|
|
@@ -55,6 +64,7 @@ from numbers_parser.formula import TableFormulas
|
|
|
55
64
|
from numbers_parser.generated import TNArchives_pb2 as TNArchives
|
|
56
65
|
from numbers_parser.generated import TSCEArchives_pb2 as TSCEArchives
|
|
57
66
|
from numbers_parser.generated import TSDArchives_pb2 as TSDArchives
|
|
67
|
+
from numbers_parser.generated import TSKArchives_pb2 as TSKArchives
|
|
58
68
|
from numbers_parser.generated import TSPArchiveMessages_pb2 as TSPArchiveMessages
|
|
59
69
|
from numbers_parser.generated import TSPMessages_pb2 as TSPMessages
|
|
60
70
|
from numbers_parser.generated import TSSArchives_pb2 as TSSArchives
|
|
@@ -87,11 +97,11 @@ class MergeCells:
|
|
|
87
97
|
def __init__(self):
|
|
88
98
|
self._references = defaultdict(lambda: False)
|
|
89
99
|
|
|
90
|
-
def add_reference(self,
|
|
91
|
-
self._references[(
|
|
100
|
+
def add_reference(self, row: int, col: int, rect: Tuple):
|
|
101
|
+
self._references[(row, col)] = MergeReference(*rect)
|
|
92
102
|
|
|
93
|
-
def add_anchor(self,
|
|
94
|
-
self._references[(
|
|
103
|
+
def add_anchor(self, row: int, col: int, size: Tuple):
|
|
104
|
+
self._references[(row, col)] = MergeAnchor(size)
|
|
95
105
|
|
|
96
106
|
def is_merge_reference(self, row_col: Tuple) -> bool:
|
|
97
107
|
return isinstance(self._references[row_col], MergeReference)
|
|
@@ -217,6 +227,9 @@ class _NumbersModel(Cacheable):
|
|
|
217
227
|
self._table_strings = DataLists(self, "stringTable", "string")
|
|
218
228
|
self._table_data = {}
|
|
219
229
|
self._styles = None
|
|
230
|
+
self._custom_formats = None
|
|
231
|
+
self._custom_format_archives = None
|
|
232
|
+
self._custom_format_ids = None
|
|
220
233
|
self._strokes = {
|
|
221
234
|
"top": defaultdict(),
|
|
222
235
|
"right": defaultdict(),
|
|
@@ -346,11 +359,126 @@ class _NumbersModel(Cacheable):
|
|
|
346
359
|
"""Return the format associated with a format ID for a particular table."""
|
|
347
360
|
return self._table_formats.lookup_value(table_id, key).format
|
|
348
361
|
|
|
349
|
-
@cache(num_args=
|
|
350
|
-
def
|
|
351
|
-
"""
|
|
362
|
+
@cache(num_args=3)
|
|
363
|
+
def format_archive(self, table_id: int, format_type: FormattingType, format: Formatting):
|
|
364
|
+
"""Create a table format from a Formatting spec and return the table format ID"""
|
|
365
|
+
attrs = {x: getattr(format, x) for x in ALLOWED_FORMATTING_PARAMETERS[format_type]}
|
|
366
|
+
attrs["format_type"] = FORMAT_TYPE_MAP[format_type]
|
|
367
|
+
|
|
368
|
+
format = TSKArchives.FormatStructArchive(**attrs)
|
|
352
369
|
return self._table_formats.lookup_key(table_id, format)
|
|
353
370
|
|
|
371
|
+
def add_custom_decimal_format_archive(self, format: CustomFormatting) -> None:
|
|
372
|
+
"""Create a custom format from the format spec"""
|
|
373
|
+
integer_format = format.integer_format
|
|
374
|
+
decimal_format = format.decimal_format
|
|
375
|
+
num_integers = format.num_integers
|
|
376
|
+
num_decimals = format.num_decimals
|
|
377
|
+
show_thousands_separator = format.show_thousands_separator
|
|
378
|
+
|
|
379
|
+
if num_integers == 0:
|
|
380
|
+
format_string = ""
|
|
381
|
+
elif integer_format == PaddingType.NONE:
|
|
382
|
+
format_string = "#" * num_integers
|
|
383
|
+
else:
|
|
384
|
+
format_string = "0" * num_integers
|
|
385
|
+
if num_integers > 6:
|
|
386
|
+
format_string = re.sub(r"(...)(...)$", r",\1,\2", format_string)
|
|
387
|
+
elif num_integers > 3:
|
|
388
|
+
format_string = re.sub(r"(...)$", r",\1", format_string)
|
|
389
|
+
if num_decimals > 0:
|
|
390
|
+
if decimal_format == PaddingType.NONE:
|
|
391
|
+
format_string += "." + "#" * num_decimals
|
|
392
|
+
else:
|
|
393
|
+
format_string += "." + "0" * num_decimals
|
|
394
|
+
|
|
395
|
+
min_integer_width = (
|
|
396
|
+
num_integers if num_integers > 0 and integer_format != PaddingType.NONE else 0
|
|
397
|
+
)
|
|
398
|
+
num_nonspace_decimal_digits = num_decimals if decimal_format == PaddingType.ZEROS else 0
|
|
399
|
+
num_nonspace_integer_digits = num_integers if integer_format == PaddingType.ZEROS else 0
|
|
400
|
+
index_from_right_last_integer = num_decimals + 1 if num_integers > 0 else num_decimals
|
|
401
|
+
# Empirically correct:
|
|
402
|
+
if index_from_right_last_integer == 1:
|
|
403
|
+
index_from_right_last_integer = 0
|
|
404
|
+
elif index_from_right_last_integer == 0:
|
|
405
|
+
index_from_right_last_integer = 1
|
|
406
|
+
decimal_width = num_decimals if decimal_format == PaddingType.SPACES else 0
|
|
407
|
+
is_complex = "0" in format_string and (
|
|
408
|
+
min_integer_width > 0 or num_nonspace_decimal_digits == 0
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
format_archive = TSKArchives.CustomFormatArchive(
|
|
412
|
+
name=format.name,
|
|
413
|
+
format_type_pre_bnc=FormatType.CUSTOM_NUMBER,
|
|
414
|
+
format_type=FormatType.CUSTOM_NUMBER,
|
|
415
|
+
default_format=TSKArchives.FormatStructArchive(
|
|
416
|
+
contains_integer_token=num_integers > 0,
|
|
417
|
+
custom_format_string=format_string,
|
|
418
|
+
decimal_width=decimal_width,
|
|
419
|
+
format_type=FormatType.CUSTOM_NUMBER,
|
|
420
|
+
fraction_accuracy=0xFFFFFFFD,
|
|
421
|
+
index_from_right_last_integer=index_from_right_last_integer,
|
|
422
|
+
is_complex=is_complex,
|
|
423
|
+
min_integer_width=min_integer_width,
|
|
424
|
+
num_hash_decimal_digits=0,
|
|
425
|
+
num_nonspace_decimal_digits=num_nonspace_decimal_digits,
|
|
426
|
+
num_nonspace_integer_digits=num_nonspace_integer_digits,
|
|
427
|
+
requires_fraction_replacement=False,
|
|
428
|
+
scale_factor=1.0,
|
|
429
|
+
show_thousands_separator=show_thousands_separator and num_integers > 0,
|
|
430
|
+
total_num_decimal_digits=decimal_width,
|
|
431
|
+
use_accounting_style=False,
|
|
432
|
+
),
|
|
433
|
+
)
|
|
434
|
+
self.add_custom_format_archive(format, format_archive)
|
|
435
|
+
|
|
436
|
+
def add_custom_datetime_format_archive(self, format: CustomFormatting) -> None:
|
|
437
|
+
format_archive = TSKArchives.CustomFormatArchive(
|
|
438
|
+
name=format.name,
|
|
439
|
+
format_type_pre_bnc=FormatType.CUSTOM_DATE,
|
|
440
|
+
format_type=FormatType.CUSTOM_DATE,
|
|
441
|
+
default_format=TSKArchives.FormatStructArchive(
|
|
442
|
+
custom_format_string=format.format,
|
|
443
|
+
format_type=FormatType.CUSTOM_DATE,
|
|
444
|
+
),
|
|
445
|
+
)
|
|
446
|
+
self.add_custom_format_archive(format, format_archive)
|
|
447
|
+
|
|
448
|
+
def add_custom_format_archive(self, format: CustomFormatting, format_archive: object) -> None:
|
|
449
|
+
format_uuid = NumbersUUID().protobuf2
|
|
450
|
+
self._custom_formats[format.name] = format
|
|
451
|
+
self._custom_format_archives[format.name] = format_archive
|
|
452
|
+
self._custom_format_uuids[format.name] = format_uuid
|
|
453
|
+
|
|
454
|
+
custom_format_list_id = self.objects[DOCUMENT_ID].super.custom_format_list.identifier
|
|
455
|
+
custom_format_list = self.objects[custom_format_list_id]
|
|
456
|
+
custom_format_list.custom_formats.append(format_archive)
|
|
457
|
+
custom_format_list.uuids.append(format_uuid)
|
|
458
|
+
|
|
459
|
+
def custom_format_id(self, table_id: int, format: CustomFormatting) -> int:
|
|
460
|
+
"""Look up the custom format and return the format ID for the table"""
|
|
461
|
+
format_type = CUSTOM_FORMAT_TYPE_MAP[format.type]
|
|
462
|
+
format_uuid = self._custom_format_uuids[format.name]
|
|
463
|
+
custom_format = TSKArchives.FormatStructArchive(
|
|
464
|
+
format_type=format_type,
|
|
465
|
+
custom_uid=TSPMessages.UUID(lower=format_uuid.lower, upper=format_uuid.upper),
|
|
466
|
+
)
|
|
467
|
+
return self._table_formats.lookup_key(table_id, custom_format)
|
|
468
|
+
|
|
469
|
+
def add_custom_text_format_archive(self, format: CustomFormatting) -> None:
|
|
470
|
+
format_string = format.format.replace("%s", CUSTOM_TEXT_PLACEHOLDER)
|
|
471
|
+
format_archive = TSKArchives.CustomFormatArchive(
|
|
472
|
+
name=format.name,
|
|
473
|
+
format_type_pre_bnc=FormatType.CUSTOM_TEXT,
|
|
474
|
+
format_type=FormatType.CUSTOM_TEXT,
|
|
475
|
+
default_format=TSKArchives.FormatStructArchive(
|
|
476
|
+
custom_format_string=format_string,
|
|
477
|
+
format_type=FormatType.CUSTOM_TEXT,
|
|
478
|
+
),
|
|
479
|
+
)
|
|
480
|
+
self.add_custom_format_archive(format, format_archive)
|
|
481
|
+
|
|
354
482
|
@cache(num_args=2)
|
|
355
483
|
def table_style(self, table_id: int, key: int) -> str:
|
|
356
484
|
"""Return the style associated with a style ID for a particular table."""
|
|
@@ -481,10 +609,10 @@ class _NumbersModel(Cacheable):
|
|
|
481
609
|
col_start = rect.origin.column
|
|
482
610
|
col_end = col_start + rect.size.num_columns - 1
|
|
483
611
|
size = (row_end - row_start + 1, col_end - col_start + 1)
|
|
484
|
-
for
|
|
485
|
-
for
|
|
612
|
+
for row in range(row_start, row_end + 1):
|
|
613
|
+
for col in range(col_start, col_end + 1):
|
|
486
614
|
self._merge_cells[table_id].add_reference(
|
|
487
|
-
|
|
615
|
+
row, col, (row_start, col_start, row_end, col_end)
|
|
488
616
|
)
|
|
489
617
|
self._merge_cells[table_id].add_anchor(row_start, col_start, size)
|
|
490
618
|
|
|
@@ -504,10 +632,10 @@ class _NumbersModel(Cacheable):
|
|
|
504
632
|
)
|
|
505
633
|
row_end = row_start + num_rows - 1
|
|
506
634
|
col_end = col_start + num_columns - 1
|
|
507
|
-
for
|
|
508
|
-
for
|
|
635
|
+
for row in range(row_start, row_end + 1):
|
|
636
|
+
for col in range(col_start, col_end + 1):
|
|
509
637
|
self._merge_cells[table_id].add_reference(
|
|
510
|
-
|
|
638
|
+
row, col, (row_start, col_start, row_end, col_end)
|
|
511
639
|
)
|
|
512
640
|
self._merge_cells[table_id].add_anchor(row_start, col_start, (num_rows, num_columns))
|
|
513
641
|
|
|
@@ -527,7 +655,7 @@ class _NumbersModel(Cacheable):
|
|
|
527
655
|
if table_uuid == self.table_base_id(table_id):
|
|
528
656
|
return table_id
|
|
529
657
|
|
|
530
|
-
def node_to_ref(self, this_table_id: int,
|
|
658
|
+
def node_to_ref(self, this_table_id: int, row: int, col: int, node):
|
|
531
659
|
if node.HasField("AST_cross_table_reference_extra_info"):
|
|
532
660
|
table_uuid = NumbersUUID(node.AST_cross_table_reference_extra_info.table_id).hex
|
|
533
661
|
other_table_id = self.table_uuids_to_id(table_uuid)
|
|
@@ -544,36 +672,34 @@ class _NumbersModel(Cacheable):
|
|
|
544
672
|
other_table_name = f"{other_sheet_name}::" + other_table_name
|
|
545
673
|
|
|
546
674
|
if node.HasField("AST_colon_tract"):
|
|
547
|
-
return self.tract_to_row_col_ref(node, other_table_name,
|
|
675
|
+
return self.tract_to_row_col_ref(node, other_table_name, row, col)
|
|
548
676
|
elif node.HasField("AST_row") and not node.HasField("AST_column"):
|
|
549
|
-
return node_to_row_ref(node, other_table_name,
|
|
677
|
+
return node_to_row_ref(node, other_table_name, row)
|
|
550
678
|
elif node.HasField("AST_column") and not node.HasField("AST_row"):
|
|
551
|
-
return node_to_col_ref(node, other_table_name,
|
|
679
|
+
return node_to_col_ref(node, other_table_name, col)
|
|
552
680
|
else:
|
|
553
|
-
return node_to_row_col_ref(node, other_table_name,
|
|
681
|
+
return node_to_row_col_ref(node, other_table_name, row, col)
|
|
554
682
|
|
|
555
|
-
def tract_to_row_col_ref(
|
|
556
|
-
self, node: object, table_name: str, row_num: int, col_num: int
|
|
557
|
-
) -> str:
|
|
683
|
+
def tract_to_row_col_ref(self, node: object, table_name: str, row: int, col: int) -> str:
|
|
558
684
|
if node.AST_sticky_bits.begin_row_is_absolute:
|
|
559
685
|
row_begin = node.AST_colon_tract.absolute_row[0].range_begin
|
|
560
686
|
else:
|
|
561
|
-
row_begin =
|
|
687
|
+
row_begin = row + node.AST_colon_tract.relative_row[0].range_begin
|
|
562
688
|
|
|
563
689
|
if node.AST_sticky_bits.end_row_is_absolute:
|
|
564
690
|
row_end = range_end(node.AST_colon_tract.absolute_row[0])
|
|
565
691
|
else:
|
|
566
|
-
row_end =
|
|
692
|
+
row_end = row + range_end(node.AST_colon_tract.relative_row[0])
|
|
567
693
|
|
|
568
694
|
if node.AST_sticky_bits.begin_column_is_absolute:
|
|
569
695
|
col_begin = node.AST_colon_tract.absolute_column[0].range_begin
|
|
570
696
|
else:
|
|
571
|
-
col_begin =
|
|
697
|
+
col_begin = col + node.AST_colon_tract.relative_column[0].range_begin
|
|
572
698
|
|
|
573
699
|
if node.AST_sticky_bits.end_column_is_absolute:
|
|
574
700
|
col_end = range_end(node.AST_colon_tract.absolute_column[0])
|
|
575
701
|
else:
|
|
576
|
-
col_end =
|
|
702
|
+
col_end = col + range_end(node.AST_colon_tract.relative_column[0])
|
|
577
703
|
|
|
578
704
|
begin_ref = xl_rowcol_to_cell(
|
|
579
705
|
row_begin,
|
|
@@ -619,29 +745,29 @@ class _NumbersModel(Cacheable):
|
|
|
619
745
|
return buffers
|
|
620
746
|
|
|
621
747
|
@cache(num_args=3)
|
|
622
|
-
def storage_buffer(self, table_id: int,
|
|
623
|
-
row_offset = self.row_storage_map(table_id)[
|
|
748
|
+
def storage_buffer(self, table_id: int, row: int, col: int) -> bytes:
|
|
749
|
+
row_offset = self.row_storage_map(table_id)[row]
|
|
624
750
|
if row_offset is None:
|
|
625
751
|
return None
|
|
626
752
|
storage_buffers = self.storage_buffers(table_id)
|
|
627
753
|
if row_offset >= len(storage_buffers):
|
|
628
754
|
return None
|
|
629
|
-
if
|
|
755
|
+
if col >= len(storage_buffers[row_offset]):
|
|
630
756
|
return None
|
|
631
|
-
return storage_buffers[row_offset][
|
|
757
|
+
return storage_buffers[row_offset][col]
|
|
632
758
|
|
|
633
759
|
def recalculate_row_headers(self, table_id: int, data: List):
|
|
634
760
|
base_data_store = self.objects[table_id].base_data_store
|
|
635
761
|
buckets = self.objects[base_data_store.rowHeaders.buckets[0].identifier]
|
|
636
762
|
clear_field_container(buckets.headers)
|
|
637
|
-
for
|
|
638
|
-
if table_id in self._row_heights and
|
|
639
|
-
height = self._row_heights[table_id][
|
|
763
|
+
for row in range(len(data)):
|
|
764
|
+
if table_id in self._row_heights and row in self._row_heights[table_id]:
|
|
765
|
+
height = self._row_heights[table_id][row]
|
|
640
766
|
else:
|
|
641
767
|
height = 0.0
|
|
642
768
|
header = TSTArchives.HeaderStorageBucket.Header(
|
|
643
|
-
index=
|
|
644
|
-
numberOfCells=len(data[
|
|
769
|
+
index=row,
|
|
770
|
+
numberOfCells=len(data[row]),
|
|
645
771
|
size=height,
|
|
646
772
|
hidingState=0,
|
|
647
773
|
)
|
|
@@ -649,8 +775,8 @@ class _NumbersModel(Cacheable):
|
|
|
649
775
|
|
|
650
776
|
def recalculate_column_headers(self, table_id: int, data: List):
|
|
651
777
|
current_column_widths = {}
|
|
652
|
-
for
|
|
653
|
-
current_column_widths[
|
|
778
|
+
for col in range(self.number_of_columns(table_id)):
|
|
779
|
+
current_column_widths[col] = self.col_width(table_id, col)
|
|
654
780
|
|
|
655
781
|
base_data_store = self.objects[table_id].base_data_store
|
|
656
782
|
buckets = self.objects[base_data_store.columnHeaders.identifier]
|
|
@@ -658,14 +784,14 @@ class _NumbersModel(Cacheable):
|
|
|
658
784
|
# Transpose data to get columns
|
|
659
785
|
col_data = [list(x) for x in zip(*data)]
|
|
660
786
|
|
|
661
|
-
for
|
|
662
|
-
num_rows = len(
|
|
663
|
-
if table_id in self._col_widths and
|
|
664
|
-
width = self._col_widths[table_id][
|
|
787
|
+
for col, cells in enumerate(col_data):
|
|
788
|
+
num_rows = len(cells) - sum([isinstance(x, MergedCell) for x in cells])
|
|
789
|
+
if table_id in self._col_widths and col in self._col_widths[table_id]:
|
|
790
|
+
width = self._col_widths[table_id][col]
|
|
665
791
|
else:
|
|
666
|
-
width = current_column_widths[
|
|
792
|
+
width = current_column_widths[col]
|
|
667
793
|
header = TSTArchives.HeaderStorageBucket.Header(
|
|
668
|
-
index=
|
|
794
|
+
index=col, numberOfCells=num_rows, size=width, hidingState=0
|
|
669
795
|
)
|
|
670
796
|
buckets.headers.append(header)
|
|
671
797
|
|
|
@@ -690,23 +816,23 @@ class _NumbersModel(Cacheable):
|
|
|
690
816
|
base_data_store.merge_region_map.CopyFrom(TSPMessages.Reference(identifier=merge_map_id))
|
|
691
817
|
|
|
692
818
|
def recalculate_row_info(
|
|
693
|
-
self, table_id: int, data: List, tile_row_offset: int,
|
|
819
|
+
self, table_id: int, data: List, tile_row_offset: int, row: int
|
|
694
820
|
) -> TSTArchives.TileRowInfo:
|
|
695
821
|
row_info = TSTArchives.TileRowInfo()
|
|
696
822
|
row_info.storage_version = 5
|
|
697
|
-
row_info.tile_row_index =
|
|
823
|
+
row_info.tile_row_index = row - tile_row_offset
|
|
698
824
|
row_info.cell_count = 0
|
|
699
825
|
cell_storage = b""
|
|
700
826
|
|
|
701
827
|
offsets = [-1] * len(data[0])
|
|
702
828
|
current_offset = 0
|
|
703
829
|
|
|
704
|
-
for
|
|
705
|
-
buffer = self.pack_cell_storage(table_id, data,
|
|
830
|
+
for col in range(len(data[row])):
|
|
831
|
+
buffer = self.pack_cell_storage(table_id, data, row, col)
|
|
706
832
|
if buffer is not None:
|
|
707
833
|
cell_storage += buffer
|
|
708
834
|
# Always use wide offsets
|
|
709
|
-
offsets[
|
|
835
|
+
offsets[col] = current_offset >> 2
|
|
710
836
|
current_offset += len(buffer)
|
|
711
837
|
|
|
712
838
|
row_info.cell_count += 1
|
|
@@ -808,8 +934,8 @@ class _NumbersModel(Cacheable):
|
|
|
808
934
|
tile_id, tile = self.objects.create_object_from_dict(
|
|
809
935
|
"Index/Tables/Tile-{}", tile_dict, TSTArchives.Tile
|
|
810
936
|
)
|
|
811
|
-
for
|
|
812
|
-
row_info = self.recalculate_row_info(table_id, data, row_start,
|
|
937
|
+
for row in range(row_start, row_end):
|
|
938
|
+
row_info = self.recalculate_row_info(table_id, data, row_start, row)
|
|
813
939
|
tile.rowInfos.append(row_info)
|
|
814
940
|
|
|
815
941
|
tile_ref = TSTArchives.TileStorage.Tile()
|
|
@@ -849,23 +975,23 @@ class _NumbersModel(Cacheable):
|
|
|
849
975
|
height += table_model.default_row_height
|
|
850
976
|
return round(height)
|
|
851
977
|
|
|
852
|
-
def row_height(self, table_id: int,
|
|
978
|
+
def row_height(self, table_id: int, row: int, height: int = None) -> int:
|
|
853
979
|
if height is not None:
|
|
854
980
|
if table_id not in self._row_heights:
|
|
855
981
|
self._row_heights[table_id] = {}
|
|
856
|
-
self._row_heights[table_id][
|
|
982
|
+
self._row_heights[table_id][row] = height
|
|
857
983
|
return height
|
|
858
984
|
|
|
859
|
-
if table_id in self._row_heights and
|
|
860
|
-
return self._row_heights[table_id][
|
|
985
|
+
if table_id in self._row_heights and row in self._row_heights[table_id]:
|
|
986
|
+
return self._row_heights[table_id][row]
|
|
861
987
|
|
|
862
988
|
table_model = self.objects[table_id]
|
|
863
989
|
bds = self.objects[table_id].base_data_store
|
|
864
990
|
bucket_id = bds.rowHeaders.buckets[0].identifier
|
|
865
991
|
buckets = self.objects[bucket_id].headers
|
|
866
992
|
bucket_map = {x.index: x for x in buckets}
|
|
867
|
-
if
|
|
868
|
-
return round(bucket_map[
|
|
993
|
+
if row in bucket_map and bucket_map[row].size != 0.0:
|
|
994
|
+
return round(bucket_map[row].size)
|
|
869
995
|
else:
|
|
870
996
|
return round(table_model.default_row_height)
|
|
871
997
|
|
|
@@ -885,23 +1011,23 @@ class _NumbersModel(Cacheable):
|
|
|
885
1011
|
width += table_model.default_column_width
|
|
886
1012
|
return round(width)
|
|
887
1013
|
|
|
888
|
-
def col_width(self, table_id: int,
|
|
1014
|
+
def col_width(self, table_id: int, col: int, width: int = None) -> int:
|
|
889
1015
|
if width is not None:
|
|
890
1016
|
if table_id not in self._col_widths:
|
|
891
1017
|
self._col_widths[table_id] = {}
|
|
892
|
-
self._col_widths[table_id][
|
|
1018
|
+
self._col_widths[table_id][col] = width
|
|
893
1019
|
return width
|
|
894
1020
|
|
|
895
|
-
if table_id in self._col_widths and
|
|
896
|
-
return self._col_widths[table_id][
|
|
1021
|
+
if table_id in self._col_widths and col in self._col_widths[table_id]:
|
|
1022
|
+
return self._col_widths[table_id][col]
|
|
897
1023
|
|
|
898
1024
|
table_model = self.objects[table_id]
|
|
899
1025
|
bds = self.objects[table_id].base_data_store
|
|
900
1026
|
bucket_id = bds.columnHeaders.identifier
|
|
901
1027
|
buckets = self.objects[bucket_id].headers
|
|
902
1028
|
bucket_map = {x.index: x for x in buckets}
|
|
903
|
-
if
|
|
904
|
-
return round(bucket_map[
|
|
1029
|
+
if col in bucket_map and bucket_map[col].size != 0.0:
|
|
1030
|
+
return round(bucket_map[col].size)
|
|
905
1031
|
else:
|
|
906
1032
|
return round(table_model.default_column_width)
|
|
907
1033
|
|
|
@@ -926,6 +1052,11 @@ class _NumbersModel(Cacheable):
|
|
|
926
1052
|
table_info.super.geometry.position.y,
|
|
927
1053
|
)
|
|
928
1054
|
|
|
1055
|
+
def is_a_pivot_table(self, table_id: int) -> bool:
|
|
1056
|
+
"""Table is a pivot table."""
|
|
1057
|
+
table_info = self.objects[self.table_info_id(table_id)]
|
|
1058
|
+
return table_info.is_a_pivot_table
|
|
1059
|
+
|
|
929
1060
|
def last_table_offset(self, sheet_id):
|
|
930
1061
|
"""Y offset of the last table in a sheet."""
|
|
931
1062
|
table_id = self.table_ids(sheet_id)[-1]
|
|
@@ -1046,10 +1177,7 @@ class _NumbersModel(Cacheable):
|
|
|
1046
1177
|
)
|
|
1047
1178
|
)
|
|
1048
1179
|
|
|
1049
|
-
data = [
|
|
1050
|
-
[EmptyCell(row_num, col_num) for col_num in range(num_cols)]
|
|
1051
|
-
for row_num in range(num_rows)
|
|
1052
|
-
]
|
|
1180
|
+
data = [[EmptyCell(row, col) for col in range(num_cols)] for row in range(num_rows)]
|
|
1053
1181
|
|
|
1054
1182
|
row_headers_id, _ = self.objects.create_object_from_dict(
|
|
1055
1183
|
"Index/Tables/HeaderStorageBucket-{}",
|
|
@@ -1184,7 +1312,7 @@ class _NumbersModel(Cacheable):
|
|
|
1184
1312
|
return self._styles
|
|
1185
1313
|
|
|
1186
1314
|
@cache(num_args=0)
|
|
1187
|
-
def available_paragraph_styles(self) ->
|
|
1315
|
+
def available_paragraph_styles(self) -> Dict[str, Style]:
|
|
1188
1316
|
theme_id = self.objects[DOCUMENT_ID].theme.identifier
|
|
1189
1317
|
presets = find_extension(self.objects[theme_id].super, "paragraph_style_presets")
|
|
1190
1318
|
presets_map = {
|
|
@@ -1337,8 +1465,8 @@ class _NumbersModel(Cacheable):
|
|
|
1337
1465
|
have changes that require a cell style.
|
|
1338
1466
|
"""
|
|
1339
1467
|
cell_styles = {}
|
|
1340
|
-
for _,
|
|
1341
|
-
for _, cell in enumerate(
|
|
1468
|
+
for _, cells in enumerate(data):
|
|
1469
|
+
for _, cell in enumerate(cells):
|
|
1342
1470
|
if cell._style is not None and cell._style._update_cell_style:
|
|
1343
1471
|
fingerprint = (
|
|
1344
1472
|
str(cell.style.alignment.vertical)
|
|
@@ -1422,7 +1550,7 @@ class _NumbersModel(Cacheable):
|
|
|
1422
1550
|
entry = self._table_styles.lookup_value(cell_storage.table_id, cell_storage.cell_style_id)
|
|
1423
1551
|
return entry.reference.identifier
|
|
1424
1552
|
|
|
1425
|
-
def custom_style_name(self) ->
|
|
1553
|
+
def custom_style_name(self) -> str:
|
|
1426
1554
|
"""Find custom styles in the current document and return the next
|
|
1427
1555
|
highest numbered style.
|
|
1428
1556
|
"""
|
|
@@ -1442,11 +1570,44 @@ class _NumbersModel(Cacheable):
|
|
|
1442
1570
|
else:
|
|
1443
1571
|
return "Custom Style 1"
|
|
1444
1572
|
|
|
1573
|
+
@property
|
|
1574
|
+
def custom_formats(self) -> Dict[str, CustomFormatting]:
|
|
1575
|
+
if self._custom_formats is None:
|
|
1576
|
+
custom_format_list_id = self.objects[DOCUMENT_ID].super.custom_format_list.identifier
|
|
1577
|
+
custom_formats = self.objects[custom_format_list_id].custom_formats
|
|
1578
|
+
custom_format_names = [x.name for x in custom_formats]
|
|
1579
|
+
custom_format_uuids = [x for x in self.objects[custom_format_list_id].uuids]
|
|
1580
|
+
self._custom_formats = {}
|
|
1581
|
+
self._custom_format_archives = {}
|
|
1582
|
+
self._custom_format_uuids = {}
|
|
1583
|
+
for i, format_name in enumerate(custom_format_names):
|
|
1584
|
+
self._custom_formats[format_name] = CustomFormatting.from_archive(custom_formats[i])
|
|
1585
|
+
self._custom_format_archives[format_name] = custom_formats[i]
|
|
1586
|
+
self._custom_format_uuids[format_name] = custom_format_uuids[i]
|
|
1587
|
+
|
|
1588
|
+
return self._custom_formats
|
|
1589
|
+
|
|
1590
|
+
def custom_format_name(self) -> str:
|
|
1591
|
+
"""Find custom formats in the current document and return the next
|
|
1592
|
+
highest numbered format.
|
|
1593
|
+
"""
|
|
1594
|
+
current_formats = self.custom_formats.keys()
|
|
1595
|
+
if "Custom Format" not in current_formats:
|
|
1596
|
+
return "Custom Format"
|
|
1597
|
+
current_formats = [
|
|
1598
|
+
m.group(1) for x in current_formats if (m := re.fullmatch(r"Custom Format (\d+)", x))
|
|
1599
|
+
]
|
|
1600
|
+
if len(current_formats) > 0:
|
|
1601
|
+
last_id = int(current_formats[-1])
|
|
1602
|
+
return f"Custom Format {last_id + 1}"
|
|
1603
|
+
else:
|
|
1604
|
+
return "Custom Format 1"
|
|
1605
|
+
|
|
1445
1606
|
def pack_cell_storage( # noqa: PLR0912, PLR0915
|
|
1446
|
-
self, table_id: int, data: List,
|
|
1607
|
+
self, table_id: int, data: List, row: int, col: int
|
|
1447
1608
|
) -> bytearray:
|
|
1448
1609
|
"""Create a storage buffer for a cell using v5 (modern) layout."""
|
|
1449
|
-
cell = data[
|
|
1610
|
+
cell = data[row][col]
|
|
1450
1611
|
if cell._style is not None:
|
|
1451
1612
|
if cell._style._text_style_obj_id is not None:
|
|
1452
1613
|
cell._storage.text_style_id = self._table_styles.lookup_key(
|
|
@@ -1516,7 +1677,7 @@ class _NumbersModel(Cacheable):
|
|
|
1516
1677
|
data_type = type(cell).__name__
|
|
1517
1678
|
table_name = self.table_name(table_id)
|
|
1518
1679
|
warn(
|
|
1519
|
-
f"@{table_name}:[{
|
|
1680
|
+
f"@{table_name}:[{row},{col}]: unsupported data type {data_type} for save",
|
|
1520
1681
|
UnsupportedWarning,
|
|
1521
1682
|
stacklevel=1,
|
|
1522
1683
|
)
|
|
@@ -1551,20 +1712,23 @@ class _NumbersModel(Cacheable):
|
|
|
1551
1712
|
flags |= 0x2000
|
|
1552
1713
|
length += 4
|
|
1553
1714
|
storage += pack("<i", cell._storage.num_format_id)
|
|
1554
|
-
storage[
|
|
1555
|
-
storage[6:8] = pack("<h", 1)
|
|
1715
|
+
storage[6] |= 1
|
|
1716
|
+
# storage[6:8] = pack("<h", 1)
|
|
1556
1717
|
if cell._storage.currency_format_id is not None:
|
|
1557
1718
|
flags |= 0x4000
|
|
1558
1719
|
length += 4
|
|
1559
1720
|
storage += pack("<i", cell._storage.currency_format_id)
|
|
1721
|
+
storage[6] |= 2
|
|
1560
1722
|
if cell._storage.date_format_id is not None:
|
|
1561
1723
|
flags |= 0x8000
|
|
1562
1724
|
length += 4
|
|
1563
1725
|
storage += pack("<i", cell._storage.date_format_id)
|
|
1726
|
+
storage[6] |= 8
|
|
1564
1727
|
if cell._storage.duration_format_id is not None:
|
|
1565
1728
|
flags |= 0x10000
|
|
1566
1729
|
length += 4
|
|
1567
1730
|
storage += pack("<i", cell._storage.duration_format_id)
|
|
1731
|
+
storage[6] |= 4
|
|
1568
1732
|
if cell._storage.text_format_id is not None:
|
|
1569
1733
|
flags |= 0x20000
|
|
1570
1734
|
length += 4
|
|
@@ -1573,6 +1737,9 @@ class _NumbersModel(Cacheable):
|
|
|
1573
1737
|
flags |= 0x40000
|
|
1574
1738
|
length += 4
|
|
1575
1739
|
storage += pack("<i", cell._storage.bool_format_id)
|
|
1740
|
+
storage[6] |= 0x20
|
|
1741
|
+
if cell._storage.string_id is not None:
|
|
1742
|
+
storage[6] |= 0x80
|
|
1576
1743
|
|
|
1577
1744
|
storage[8:12] = pack("<i", flags)
|
|
1578
1745
|
if len(storage) < 32:
|
|
@@ -1585,12 +1752,12 @@ class _NumbersModel(Cacheable):
|
|
|
1585
1752
|
return TableFormulas(self, table_id)
|
|
1586
1753
|
|
|
1587
1754
|
@cache(num_args=3)
|
|
1588
|
-
def table_cell_decode(self, table_id: int,
|
|
1589
|
-
buffer = self.storage_buffer(table_id,
|
|
1755
|
+
def table_cell_decode(self, table_id: int, row: int, col: int) -> Dict:
|
|
1756
|
+
buffer = self.storage_buffer(table_id, row, col)
|
|
1590
1757
|
if buffer is None:
|
|
1591
1758
|
return None
|
|
1592
1759
|
|
|
1593
|
-
return CellStorage(self, table_id, buffer,
|
|
1760
|
+
return CellStorage(self, table_id, buffer, row, col)
|
|
1594
1761
|
|
|
1595
1762
|
@cache(num_args=2)
|
|
1596
1763
|
def table_rich_text(self, table_id: int, string_key: int) -> Dict:
|
|
@@ -1691,14 +1858,14 @@ class _NumbersModel(Cacheable):
|
|
|
1691
1858
|
return self.table_style(cell_storage.table_id, cell_storage.text_style_id)
|
|
1692
1859
|
|
|
1693
1860
|
table_model = self.objects[cell_storage.table_id]
|
|
1694
|
-
if cell_storage.
|
|
1861
|
+
if cell_storage.row in range(table_model.number_of_header_rows):
|
|
1695
1862
|
return self.objects[table_model.header_row_text_style.identifier]
|
|
1696
|
-
elif cell_storage.
|
|
1863
|
+
elif cell_storage.col in range(table_model.number_of_header_columns):
|
|
1697
1864
|
return self.objects[table_model.header_column_text_style.identifier]
|
|
1698
1865
|
elif table_model.number_of_footer_rows > 0:
|
|
1699
1866
|
start_row_num = table_model.number_of_rows - table_model.number_of_footer_rows
|
|
1700
1867
|
end_row_num = start_row_num + table_model.number_of_footer_rows
|
|
1701
|
-
if cell_storage.
|
|
1868
|
+
if cell_storage.row in range(start_row_num, end_row_num):
|
|
1702
1869
|
return self.objects[table_model.footer_row_text_style.identifier]
|
|
1703
1870
|
return self.objects[table_model.body_text_style.identifier]
|
|
1704
1871
|
|
|
@@ -1831,19 +1998,19 @@ class _NumbersModel(Cacheable):
|
|
|
1831
1998
|
else:
|
|
1832
1999
|
return "none"
|
|
1833
2000
|
|
|
1834
|
-
def cell_for_stroke(self, table_id: int, side: str,
|
|
2001
|
+
def cell_for_stroke(self, table_id: int, side: str, row: int, col: int) -> object:
|
|
1835
2002
|
data = self._table_data[table_id]
|
|
1836
|
-
if
|
|
2003
|
+
if row < 0 or col < 0:
|
|
1837
2004
|
return None
|
|
1838
|
-
if
|
|
2005
|
+
if row >= len(data) or col >= len(data[row]):
|
|
1839
2006
|
return None
|
|
1840
|
-
cell = self._table_data[table_id][
|
|
2007
|
+
cell = self._table_data[table_id][row][col]
|
|
1841
2008
|
if isinstance(cell, MergedCell):
|
|
1842
2009
|
if (
|
|
1843
|
-
(side == "top" and
|
|
1844
|
-
or (side == "right" and
|
|
1845
|
-
or (side == "bottom" and
|
|
1846
|
-
or (side == "left" and
|
|
2010
|
+
(side == "top" and row == cell.row_start)
|
|
2011
|
+
or (side == "right" and col == cell.col_end)
|
|
2012
|
+
or (side == "bottom" and row == cell.row_end)
|
|
2013
|
+
or (side == "left" and col == cell.col_start)
|
|
1847
2014
|
):
|
|
1848
2015
|
return cell
|
|
1849
2016
|
elif cell.is_merged:
|
|
@@ -1854,28 +2021,28 @@ class _NumbersModel(Cacheable):
|
|
|
1854
2021
|
return None
|
|
1855
2022
|
|
|
1856
2023
|
def set_cell_border( # noqa: PLR0913
|
|
1857
|
-
self, table_id: int,
|
|
2024
|
+
self, table_id: int, row: int, col: int, side: str, border_value: Border
|
|
1858
2025
|
):
|
|
1859
2026
|
"""Set the 2 borders adjacent to a stroke if within the table range."""
|
|
1860
2027
|
if side == "top":
|
|
1861
|
-
if (cell := self.cell_for_stroke(table_id, "top",
|
|
2028
|
+
if (cell := self.cell_for_stroke(table_id, "top", row, col)) is not None:
|
|
1862
2029
|
cell._border.top = border_value
|
|
1863
|
-
if (cell := self.cell_for_stroke(table_id, "bottom",
|
|
2030
|
+
if (cell := self.cell_for_stroke(table_id, "bottom", row - 1, col)) is not None:
|
|
1864
2031
|
cell._border.bottom = border_value
|
|
1865
2032
|
elif side == "right":
|
|
1866
|
-
if (cell := self.cell_for_stroke(table_id, "right",
|
|
2033
|
+
if (cell := self.cell_for_stroke(table_id, "right", row, col)) is not None:
|
|
1867
2034
|
cell._border.right = border_value
|
|
1868
|
-
if (cell := self.cell_for_stroke(table_id, "left",
|
|
2035
|
+
if (cell := self.cell_for_stroke(table_id, "left", row, col + 1)) is not None:
|
|
1869
2036
|
cell._border.left = border_value
|
|
1870
2037
|
elif side == "bottom":
|
|
1871
|
-
if (cell := self.cell_for_stroke(table_id, "bottom",
|
|
2038
|
+
if (cell := self.cell_for_stroke(table_id, "bottom", row, col)) is not None:
|
|
1872
2039
|
cell._border.bottom = border_value
|
|
1873
|
-
if (cell := self.cell_for_stroke(table_id, "top",
|
|
2040
|
+
if (cell := self.cell_for_stroke(table_id, "top", row + 1, col)) is not None:
|
|
1874
2041
|
cell._border.top = border_value
|
|
1875
2042
|
else: # left border
|
|
1876
|
-
if (cell := self.cell_for_stroke(table_id, "left",
|
|
2043
|
+
if (cell := self.cell_for_stroke(table_id, "left", row, col)) is not None:
|
|
1877
2044
|
cell._border.left = border_value
|
|
1878
|
-
if (cell := self.cell_for_stroke(table_id, "right",
|
|
2045
|
+
if (cell := self.cell_for_stroke(table_id, "right", row, col - 1)) is not None:
|
|
1879
2046
|
cell._border.right = border_value
|
|
1880
2047
|
|
|
1881
2048
|
def extract_strokes_in_layers(self, table_id: int, layer_ids: List, side: str):
|
|
@@ -1891,13 +2058,13 @@ class _NumbersModel(Cacheable):
|
|
|
1891
2058
|
if side in ["top", "bottom"]:
|
|
1892
2059
|
start_row = stroke_layer.row_column_index
|
|
1893
2060
|
start_column = stroke_run.origin
|
|
1894
|
-
for
|
|
1895
|
-
self.set_cell_border(table_id, start_row,
|
|
2061
|
+
for col in range(start_column, start_column + stroke_run.length):
|
|
2062
|
+
self.set_cell_border(table_id, start_row, col, side, border_value)
|
|
1896
2063
|
else:
|
|
1897
2064
|
start_row = stroke_run.origin
|
|
1898
2065
|
start_column = stroke_layer.row_column_index
|
|
1899
|
-
for
|
|
1900
|
-
self.set_cell_border(table_id,
|
|
2066
|
+
for row in range(start_row, start_row + stroke_run.length):
|
|
2067
|
+
self.set_cell_border(table_id, row, start_column, side, border_value)
|
|
1901
2068
|
|
|
1902
2069
|
@cache()
|
|
1903
2070
|
def extract_strokes(self, table_id: int):
|
|
@@ -1968,8 +2135,8 @@ class _NumbersModel(Cacheable):
|
|
|
1968
2135
|
def add_stroke( # noqa: PLR0913
|
|
1969
2136
|
self,
|
|
1970
2137
|
table_id: int,
|
|
1971
|
-
|
|
1972
|
-
|
|
2138
|
+
row: int,
|
|
2139
|
+
col: int,
|
|
1973
2140
|
side: str,
|
|
1974
2141
|
border_value: Border,
|
|
1975
2142
|
length: int,
|
|
@@ -1983,20 +2150,20 @@ class _NumbersModel(Cacheable):
|
|
|
1983
2150
|
|
|
1984
2151
|
if side == "top":
|
|
1985
2152
|
layer_ids = sidecar_obj.top_row_stroke_layers
|
|
1986
|
-
row_column_index =
|
|
1987
|
-
origin =
|
|
2153
|
+
row_column_index = row
|
|
2154
|
+
origin = col
|
|
1988
2155
|
elif side == "right":
|
|
1989
2156
|
layer_ids = sidecar_obj.right_column_stroke_layers
|
|
1990
|
-
row_column_index =
|
|
1991
|
-
origin =
|
|
2157
|
+
row_column_index = col
|
|
2158
|
+
origin = row
|
|
1992
2159
|
elif side == "bottom":
|
|
1993
2160
|
layer_ids = sidecar_obj.bottom_row_stroke_layers
|
|
1994
|
-
row_column_index =
|
|
1995
|
-
origin =
|
|
2161
|
+
row_column_index = row
|
|
2162
|
+
origin = col
|
|
1996
2163
|
else: # left border
|
|
1997
2164
|
layer_ids = sidecar_obj.left_column_stroke_layers
|
|
1998
|
-
row_column_index =
|
|
1999
|
-
origin =
|
|
2165
|
+
row_column_index = col
|
|
2166
|
+
origin = row
|
|
2000
2167
|
|
|
2001
2168
|
stroke_layer = None
|
|
2002
2169
|
for layer_id in layer_ids:
|
|
@@ -2065,8 +2232,8 @@ def formatted_number(number_type, index):
|
|
|
2065
2232
|
return bullet_char
|
|
2066
2233
|
|
|
2067
2234
|
|
|
2068
|
-
def node_to_col_ref(node: object, table_name: str,
|
|
2069
|
-
col = node.AST_column.column if node.AST_column.absolute else
|
|
2235
|
+
def node_to_col_ref(node: object, table_name: str, col: int) -> str:
|
|
2236
|
+
col = node.AST_column.column if node.AST_column.absolute else col + node.AST_column.column
|
|
2070
2237
|
|
|
2071
2238
|
col_name = xl_col_to_name(col, node.AST_column.absolute)
|
|
2072
2239
|
if table_name is not None:
|
|
@@ -2075,8 +2242,8 @@ def node_to_col_ref(node: object, table_name: str, col_num: int) -> str:
|
|
|
2075
2242
|
return col_name
|
|
2076
2243
|
|
|
2077
2244
|
|
|
2078
|
-
def node_to_row_ref(node: object, table_name: str,
|
|
2079
|
-
row = node.AST_row.row if node.AST_row.absolute else
|
|
2245
|
+
def node_to_row_ref(node: object, table_name: str, row: int) -> str:
|
|
2246
|
+
row = node.AST_row.row if node.AST_row.absolute else row + node.AST_row.row
|
|
2080
2247
|
|
|
2081
2248
|
row_name = f"${row+1}" if node.AST_row.absolute else f"{row+1}"
|
|
2082
2249
|
if table_name is not None:
|
|
@@ -2085,9 +2252,9 @@ def node_to_row_ref(node: object, table_name: str, row_num: int) -> str:
|
|
|
2085
2252
|
return f"{row_name}:{row_name}"
|
|
2086
2253
|
|
|
2087
2254
|
|
|
2088
|
-
def node_to_row_col_ref(node: object, table_name: str,
|
|
2089
|
-
row = node.AST_row.row if node.AST_row.absolute else
|
|
2090
|
-
col = node.AST_column.column if node.AST_column.absolute else
|
|
2255
|
+
def node_to_row_col_ref(node: object, table_name: str, row: int, col: int) -> str:
|
|
2256
|
+
row = node.AST_row.row if node.AST_row.absolute else row + node.AST_row.row
|
|
2257
|
+
col = node.AST_column.column if node.AST_column.absolute else col + node.AST_column.column
|
|
2091
2258
|
|
|
2092
2259
|
ref = xl_rowcol_to_cell(
|
|
2093
2260
|
row,
|
|
@@ -2120,23 +2287,23 @@ def get_storage_buffers_for_row(
|
|
|
2120
2287
|
offsets = [o * 4 for o in offsets]
|
|
2121
2288
|
|
|
2122
2289
|
data = []
|
|
2123
|
-
for
|
|
2124
|
-
if
|
|
2290
|
+
for col in range(num_cols):
|
|
2291
|
+
if col >= len(offsets):
|
|
2125
2292
|
break
|
|
2126
2293
|
|
|
2127
|
-
start = offsets[
|
|
2294
|
+
start = offsets[col]
|
|
2128
2295
|
if start < 0:
|
|
2129
2296
|
data.append(None)
|
|
2130
2297
|
continue
|
|
2131
2298
|
|
|
2132
|
-
if
|
|
2299
|
+
if col == (len(offsets) - 1):
|
|
2133
2300
|
end = len(storage_buffer)
|
|
2134
2301
|
else:
|
|
2135
2302
|
end = None
|
|
2136
2303
|
# Find next positive offset
|
|
2137
|
-
for i, x in enumerate(offsets[
|
|
2304
|
+
for i, x in enumerate(offsets[col + 1 :]):
|
|
2138
2305
|
if x >= 0:
|
|
2139
|
-
end = offsets[
|
|
2306
|
+
end = offsets[col + i + 1]
|
|
2140
2307
|
break
|
|
2141
2308
|
if end is None:
|
|
2142
2309
|
end = len(storage_buffer)
|