numbers-parser 4.10.3__py3-none-any.whl → 4.10.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- numbers_parser/__init__.py +1 -1
- numbers_parser/_cat_numbers.py +4 -4
- numbers_parser/_unpack_numbers.py +5 -5
- numbers_parser/cell.py +119 -119
- numbers_parser/constants.py +5 -6
- numbers_parser/containers.py +13 -7
- numbers_parser/document.py +135 -113
- numbers_parser/exceptions.py +0 -7
- numbers_parser/formula.py +3 -4
- numbers_parser/generated/TNArchives_pb2.py +24 -26
- numbers_parser/generated/TSAArchives_pb2.py +26 -28
- numbers_parser/generated/TSCEArchives_pb2.py +8 -10
- numbers_parser/generated/TSCHArchives_GEN_pb2.py +28 -34
- numbers_parser/generated/TSCHArchives_pb2.py +61 -65
- numbers_parser/generated/TSDArchives_pb2.py +100 -110
- numbers_parser/generated/TSKArchives_pb2.py +168 -170
- numbers_parser/generated/TSPArchiveMessages_pb2.py +32 -34
- numbers_parser/generated/TSPMessages_pb2.py +40 -40
- numbers_parser/generated/TSSArchives_pb2.py +36 -36
- numbers_parser/generated/TSTArchives_pb2.py +321 -325
- numbers_parser/generated/TSTStylePropertyArchiving_pb2.py +8 -10
- numbers_parser/generated/TSWPArchives_pb2.py +250 -286
- numbers_parser/generated/TSWPCommandArchives_pb2.py +95 -95
- numbers_parser/iwafile.py +16 -16
- numbers_parser/iwork.py +35 -29
- numbers_parser/model.py +77 -55
- numbers_parser/numbers_uuid.py +6 -4
- {numbers_parser-4.10.3.dist-info → numbers_parser-4.10.5.dist-info}/METADATA +7 -7
- {numbers_parser-4.10.3.dist-info → numbers_parser-4.10.5.dist-info}/RECORD +32 -32
- {numbers_parser-4.10.3.dist-info → numbers_parser-4.10.5.dist-info}/LICENSE.rst +0 -0
- {numbers_parser-4.10.3.dist-info → numbers_parser-4.10.5.dist-info}/WHEEL +0 -0
- {numbers_parser-4.10.3.dist-info → numbers_parser-4.10.5.dist-info}/entry_points.txt +0 -0
numbers_parser/cell.py
CHANGED
|
@@ -10,7 +10,7 @@ from fractions import Fraction
|
|
|
10
10
|
from hashlib import sha1
|
|
11
11
|
from os.path import basename
|
|
12
12
|
from struct import pack, unpack
|
|
13
|
-
from typing import Any, List, Tuple, Union
|
|
13
|
+
from typing import Any, List, Optional, Tuple, Union
|
|
14
14
|
from warnings import warn
|
|
15
15
|
|
|
16
16
|
import sigfig
|
|
@@ -104,8 +104,7 @@ __all__ = [
|
|
|
104
104
|
|
|
105
105
|
|
|
106
106
|
class BackgroundImage:
|
|
107
|
-
"""
|
|
108
|
-
A named document style that can be applied to cells.
|
|
107
|
+
"""A named document style that can be applied to cells.
|
|
109
108
|
|
|
110
109
|
.. code-block:: python
|
|
111
110
|
|
|
@@ -129,7 +128,7 @@ class BackgroundImage:
|
|
|
129
128
|
Path to the image file.
|
|
130
129
|
"""
|
|
131
130
|
|
|
132
|
-
def __init__(self, data: bytes = None, filename: str = None):
|
|
131
|
+
def __init__(self, data: Optional[bytes] = None, filename: Optional[str] = None) -> None:
|
|
133
132
|
self._data = data
|
|
134
133
|
self._filename = basename(filename)
|
|
135
134
|
|
|
@@ -180,13 +179,15 @@ class Alignment(_Alignment):
|
|
|
180
179
|
if isinstance(horizontal, str):
|
|
181
180
|
horizontal = horizontal.lower()
|
|
182
181
|
if horizontal not in HORIZONTAL_MAP:
|
|
183
|
-
|
|
182
|
+
msg = "invalid horizontal alignment"
|
|
183
|
+
raise TypeError(msg)
|
|
184
184
|
horizontal = HORIZONTAL_MAP[horizontal]
|
|
185
185
|
|
|
186
186
|
if isinstance(vertical, str):
|
|
187
187
|
vertical = vertical.lower()
|
|
188
188
|
if vertical not in VERTICAL_MAP:
|
|
189
|
-
|
|
189
|
+
msg = "invalid vertical alignment"
|
|
190
|
+
raise TypeError(msg)
|
|
190
191
|
vertical = VERTICAL_MAP[vertical]
|
|
191
192
|
|
|
192
193
|
return super(_Alignment, cls).__new__(cls, (horizontal, vertical))
|
|
@@ -199,8 +200,7 @@ RGB = namedtuple("RGB", ["r", "g", "b"])
|
|
|
199
200
|
|
|
200
201
|
@dataclass
|
|
201
202
|
class Style:
|
|
202
|
-
"""
|
|
203
|
-
A named document style that can be applied to cells.
|
|
203
|
+
"""A named document style that can be applied to cells.
|
|
204
204
|
|
|
205
205
|
Parameters
|
|
206
206
|
----------
|
|
@@ -296,10 +296,7 @@ class Style:
|
|
|
296
296
|
|
|
297
297
|
@classmethod
|
|
298
298
|
def from_storage(cls, cell: object, model: object):
|
|
299
|
-
if cell._image_data is not None
|
|
300
|
-
bg_image = BackgroundImage(*cell._image_data)
|
|
301
|
-
else:
|
|
302
|
-
bg_image = None
|
|
299
|
+
bg_image = BackgroundImage(*cell._image_data) if cell._image_data is not None else None
|
|
303
300
|
return Style(
|
|
304
301
|
alignment=model.cell_alignment(cell),
|
|
305
302
|
bg_image=bg_image,
|
|
@@ -326,13 +323,16 @@ class Style:
|
|
|
326
323
|
self.font_color = rgb_color(self.font_color)
|
|
327
324
|
|
|
328
325
|
if not isinstance(self.font_size, float):
|
|
329
|
-
|
|
326
|
+
msg = "size must be a float number of points"
|
|
327
|
+
raise TypeError(msg)
|
|
330
328
|
if not isinstance(self.font_name, str):
|
|
331
|
-
|
|
329
|
+
msg = "font name must be a string"
|
|
330
|
+
raise TypeError(msg)
|
|
332
331
|
|
|
333
332
|
for attr in ["bold", "italic", "underline", "strikethrough"]:
|
|
334
333
|
if not isinstance(getattr(self, attr), bool):
|
|
335
|
-
|
|
334
|
+
msg = f"{attr} argument must be boolean"
|
|
335
|
+
raise TypeError(msg)
|
|
336
336
|
|
|
337
337
|
def __setattr__(self, name: str, value: Any) -> None:
|
|
338
338
|
"""Detect changes to cell styles and flag the style for
|
|
@@ -359,11 +359,13 @@ def rgb_color(color) -> RGB:
|
|
|
359
359
|
return color
|
|
360
360
|
if isinstance(color, tuple):
|
|
361
361
|
if not (len(color) == 3 and all(isinstance(x, int) for x in color)):
|
|
362
|
-
|
|
362
|
+
msg = "RGB color must be an RGB or a tuple of 3 integers"
|
|
363
|
+
raise TypeError(msg)
|
|
363
364
|
return RGB(*color)
|
|
364
365
|
elif isinstance(color, list):
|
|
365
366
|
return [rgb_color(c) for c in color]
|
|
366
|
-
|
|
367
|
+
msg = "RGB color must be an RGB or a tuple of 3 integers"
|
|
368
|
+
raise TypeError(msg)
|
|
367
369
|
|
|
368
370
|
|
|
369
371
|
def alignment(value) -> Alignment:
|
|
@@ -374,9 +376,11 @@ def alignment(value) -> Alignment:
|
|
|
374
376
|
return value
|
|
375
377
|
if isinstance(value, tuple):
|
|
376
378
|
if not (len(value) == 2 and all(isinstance(x, (int, str)) for x in value)):
|
|
377
|
-
|
|
379
|
+
msg = "Alignment must be an Alignment or a tuple of 2 integers/strings"
|
|
380
|
+
raise TypeError(msg)
|
|
378
381
|
return Alignment(*value)
|
|
379
|
-
|
|
382
|
+
msg = "Alignment must be an Alignment or a tuple of 2 integers/strings"
|
|
383
|
+
raise TypeError(msg)
|
|
380
384
|
|
|
381
385
|
|
|
382
386
|
BORDER_STYLE_MAP = {"solid": 0, "dashes": 1, "dots": 2, "none": 3}
|
|
@@ -390,8 +394,7 @@ class BorderType(IntEnum):
|
|
|
390
394
|
|
|
391
395
|
|
|
392
396
|
class Border:
|
|
393
|
-
"""
|
|
394
|
-
Create a cell border to use with the :py:class:`~numbers_parser.Table` method
|
|
397
|
+
"""Create a cell border to use with the :py:class:`~numbers_parser.Table` method
|
|
395
398
|
:py:meth:`~numbers_parser.Table.set_cell_border`.
|
|
396
399
|
|
|
397
400
|
.. code-block:: python
|
|
@@ -426,9 +429,10 @@ class Border:
|
|
|
426
429
|
color: RGB = None,
|
|
427
430
|
style: BorderType = None,
|
|
428
431
|
_order: int = 0,
|
|
429
|
-
):
|
|
432
|
+
) -> None:
|
|
430
433
|
if not isinstance(width, float):
|
|
431
|
-
|
|
434
|
+
msg = "width must be a float number of points"
|
|
435
|
+
raise TypeError(msg)
|
|
432
436
|
self.width = width
|
|
433
437
|
|
|
434
438
|
if color is None:
|
|
@@ -440,7 +444,8 @@ class Border:
|
|
|
440
444
|
if isinstance(style, str):
|
|
441
445
|
style = style.lower()
|
|
442
446
|
if style not in BORDER_STYLE_MAP:
|
|
443
|
-
|
|
447
|
+
msg = "invalid border style"
|
|
448
|
+
raise TypeError(msg)
|
|
444
449
|
self.style = BORDER_STYLE_MAP[style]
|
|
445
450
|
else:
|
|
446
451
|
self.style = style
|
|
@@ -453,7 +458,7 @@ class Border:
|
|
|
453
458
|
|
|
454
459
|
def __eq__(self, value: object) -> bool:
|
|
455
460
|
return all(
|
|
456
|
-
[self.width == value.width, self.color == value.color, self.style == value.style]
|
|
461
|
+
[self.width == value.width, self.color == value.color, self.style == value.style],
|
|
457
462
|
)
|
|
458
463
|
|
|
459
464
|
|
|
@@ -464,7 +469,7 @@ class CellBorder:
|
|
|
464
469
|
right_merged: bool = False,
|
|
465
470
|
bottom_merged: bool = False,
|
|
466
471
|
left_merged: bool = False,
|
|
467
|
-
):
|
|
472
|
+
) -> None:
|
|
468
473
|
self._top = None
|
|
469
474
|
self._right = None
|
|
470
475
|
self._bottom = None
|
|
@@ -538,14 +543,14 @@ class CellBorder:
|
|
|
538
543
|
class MergeReference:
|
|
539
544
|
"""Cell reference for cells eliminated by a merge."""
|
|
540
545
|
|
|
541
|
-
def __init__(self, row_start: int, col_start: int, row_end: int, col_end: int):
|
|
546
|
+
def __init__(self, row_start: int, col_start: int, row_end: int, col_end: int) -> None:
|
|
542
547
|
self.rect = (row_start, col_start, row_end, col_end)
|
|
543
548
|
|
|
544
549
|
|
|
545
550
|
class MergeAnchor:
|
|
546
551
|
"""Cell reference for the merged cell."""
|
|
547
552
|
|
|
548
|
-
def __init__(self, size: Tuple):
|
|
553
|
+
def __init__(self, size: Tuple) -> None:
|
|
549
554
|
self.size = size
|
|
550
555
|
|
|
551
556
|
|
|
@@ -574,20 +579,18 @@ class CellStorageFlags:
|
|
|
574
579
|
fields = [
|
|
575
580
|
f"{k[1:]}={v}" for k, v in asdict(self).items() if k.endswith("_id") and v is not None
|
|
576
581
|
]
|
|
577
|
-
|
|
578
|
-
return fields
|
|
582
|
+
return ", ".join([x for x in fields if x if not None])
|
|
579
583
|
|
|
580
584
|
def flags(self):
|
|
581
585
|
return [x.name for x in fields(self)]
|
|
582
586
|
|
|
583
587
|
|
|
584
588
|
class Cell(CellStorageFlags, Cacheable):
|
|
585
|
-
"""
|
|
586
|
-
|
|
587
|
-
Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
|
|
589
|
+
""".. NOTE::
|
|
590
|
+
Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
|
|
588
591
|
"""
|
|
589
592
|
|
|
590
|
-
def __init__(self, row: int, col: int, value):
|
|
593
|
+
def __init__(self, row: int, col: int, value) -> None:
|
|
591
594
|
self._value = value
|
|
592
595
|
self.row = row
|
|
593
596
|
self.col = col
|
|
@@ -599,14 +602,13 @@ class Cell(CellStorageFlags, Cacheable):
|
|
|
599
602
|
self._seconds = None
|
|
600
603
|
super().__init__()
|
|
601
604
|
|
|
602
|
-
def __str__(self):
|
|
605
|
+
def __str__(self) -> str:
|
|
603
606
|
table_name = self._model.table_name(self._table_id)
|
|
604
607
|
sheet_name = self._model.sheet_name(self._model.table_id_to_sheet_id(self._table_id))
|
|
605
608
|
cell_str = f"{sheet_name}@{table_name}[{self.row},{self.col}]:"
|
|
606
609
|
cell_str += f"table_id={self._table_id}, type={self._type.name}, "
|
|
607
610
|
cell_str += f"value={self._value}, flags={self._flags:08x}, extras={self._extras:04x}"
|
|
608
|
-
|
|
609
|
-
return cell_str
|
|
611
|
+
return ", ".join([cell_str, super().__str__()])
|
|
610
612
|
|
|
611
613
|
@property
|
|
612
614
|
def image_filename(self):
|
|
@@ -643,14 +645,14 @@ class Cell(CellStorageFlags, Cacheable):
|
|
|
643
645
|
@property
|
|
644
646
|
@cache(num_args=0)
|
|
645
647
|
def formula(self) -> str:
|
|
646
|
-
"""
|
|
647
|
-
str: The formula in a cell.
|
|
648
|
+
"""str: The formula in a cell.
|
|
648
649
|
|
|
649
650
|
Formula evaluation relies on Numbers storing current values which should
|
|
650
651
|
usually be the case. In cells containing a formula, :py:meth:`numbers_parser.Cell.value`
|
|
651
652
|
returns computed value of the formula.
|
|
652
653
|
|
|
653
|
-
Returns
|
|
654
|
+
Returns
|
|
655
|
+
-------
|
|
654
656
|
str:
|
|
655
657
|
The text of the foruma in a cell, or `None` if there is no formula
|
|
656
658
|
present in a cell.
|
|
@@ -668,8 +670,7 @@ class Cell(CellStorageFlags, Cacheable):
|
|
|
668
670
|
|
|
669
671
|
@property
|
|
670
672
|
def bullets(self) -> Union[List[str], None]:
|
|
671
|
-
r"""
|
|
672
|
-
List[str] | None: The bullets in a cell, or ``None``
|
|
673
|
+
r"""List[str] | None: The bullets in a cell, or ``None``.
|
|
673
674
|
|
|
674
675
|
Cells that contain bulleted or numbered lists are identified
|
|
675
676
|
by :py:attr:`numbers_parser.Cell.is_bulleted`. For these cells,
|
|
@@ -678,9 +679,8 @@ class Cell(CellStorageFlags, Cacheable):
|
|
|
678
679
|
bullet or numbering character. Newlines are not included in the
|
|
679
680
|
bullet list.
|
|
680
681
|
|
|
681
|
-
Example
|
|
682
|
+
Example:
|
|
682
683
|
-------
|
|
683
|
-
|
|
684
684
|
.. code-block:: python
|
|
685
685
|
|
|
686
686
|
doc = Document("bullets.numbers")
|
|
@@ -698,8 +698,7 @@ class Cell(CellStorageFlags, Cacheable):
|
|
|
698
698
|
|
|
699
699
|
@property
|
|
700
700
|
def formatted_value(self) -> str:
|
|
701
|
-
"""
|
|
702
|
-
str: The formatted value of the cell as it appears in Numbers.
|
|
701
|
+
"""str: The formatted value of the cell as it appears in Numbers.
|
|
703
702
|
|
|
704
703
|
Interactive elements are converted into a suitable text format where
|
|
705
704
|
supported, or as their number values where there is no suitable
|
|
@@ -743,6 +742,7 @@ class Cell(CellStorageFlags, Cacheable):
|
|
|
743
742
|
"""Style | None: The :class:`Style` associated with the cell or ``None``.
|
|
744
743
|
|
|
745
744
|
Warns:
|
|
745
|
+
-----
|
|
746
746
|
UnsupportedWarning: On assignment; use
|
|
747
747
|
:py:meth:`numbers_parser.Table.set_cell_style` instead.
|
|
748
748
|
"""
|
|
@@ -763,6 +763,7 @@ class Cell(CellStorageFlags, Cacheable):
|
|
|
763
763
|
"""CellBorder| None: The :class:`CellBorder` associated with the cell or ``None``.
|
|
764
764
|
|
|
765
765
|
Warns:
|
|
766
|
+
-----
|
|
766
767
|
UnsupportedWarning: On assignment; use
|
|
767
768
|
:py:meth:`numbers_parser.Table.set_cell_border` instead.
|
|
768
769
|
"""
|
|
@@ -818,8 +819,8 @@ class Cell(CellStorageFlags, Cacheable):
|
|
|
818
819
|
return cell
|
|
819
820
|
|
|
820
821
|
@classmethod
|
|
821
|
-
def _from_storage( # noqa: PLR0913,
|
|
822
|
-
cls, table_id: int, row: int, col: int, buffer: bytearray, model: object
|
|
822
|
+
def _from_storage( # noqa: PLR0913, PLR0912
|
|
823
|
+
cls, table_id: int, row: int, col: int, buffer: bytearray, model: object,
|
|
823
824
|
) -> None:
|
|
824
825
|
d128 = None
|
|
825
826
|
double = None
|
|
@@ -827,7 +828,8 @@ class Cell(CellStorageFlags, Cacheable):
|
|
|
827
828
|
|
|
828
829
|
version = buffer[0]
|
|
829
830
|
if version != 5:
|
|
830
|
-
|
|
831
|
+
msg = f"Cell storage version {version} is unsupported"
|
|
832
|
+
raise UnsupportedError(msg)
|
|
831
833
|
|
|
832
834
|
offset = 12
|
|
833
835
|
storage_flags = CellStorageFlags()
|
|
@@ -921,7 +923,8 @@ class Cell(CellStorageFlags, Cacheable):
|
|
|
921
923
|
elif cell_type == CURRENCY_CELL_TYPE:
|
|
922
924
|
cell = NumberCell(row, col, d128, cell_type=CellType.CURRENCY)
|
|
923
925
|
else:
|
|
924
|
-
|
|
926
|
+
msg = f"Cell type ID {cell_type} is not recognised"
|
|
927
|
+
raise UnsupportedError(msg)
|
|
925
928
|
|
|
926
929
|
cell._copy_flags(storage_flags)
|
|
927
930
|
cell._buffer = buffer
|
|
@@ -1136,8 +1139,8 @@ class Cell(CellStorageFlags, Cacheable):
|
|
|
1136
1139
|
|
|
1137
1140
|
image_id = style.cell_properties.cell_fill.image.imagedata.identifier
|
|
1138
1141
|
datas = self._model.objects[PACKAGE_ID].datas
|
|
1139
|
-
stored_filename =
|
|
1140
|
-
preferred_filename =
|
|
1142
|
+
stored_filename = next(x.file_name for x in datas if x.identifier == image_id)
|
|
1143
|
+
preferred_filename = next(x.preferred_file_name for x in datas if x.identifier == image_id)
|
|
1141
1144
|
all_paths = self._model.objects.file_store.keys()
|
|
1142
1145
|
image_pathnames = [x for x in all_paths if x == f"Data/{stored_filename}"]
|
|
1143
1146
|
|
|
@@ -1147,6 +1150,7 @@ class Cell(CellStorageFlags, Cacheable):
|
|
|
1147
1150
|
RuntimeWarning,
|
|
1148
1151
|
stacklevel=3,
|
|
1149
1152
|
)
|
|
1153
|
+
return None
|
|
1150
1154
|
else:
|
|
1151
1155
|
image_data = self._model.objects.file_store[image_pathnames[0]]
|
|
1152
1156
|
digest = sha1(image_data).digest()
|
|
@@ -1182,7 +1186,7 @@ class Cell(CellStorageFlags, Cacheable):
|
|
|
1182
1186
|
)
|
|
1183
1187
|
else:
|
|
1184
1188
|
formatted_value = _decode_number_format(
|
|
1185
|
-
custom_format, self._d128, format_map[format_uuid].name
|
|
1189
|
+
custom_format, self._d128, format_map[format_uuid].name,
|
|
1186
1190
|
)
|
|
1187
1191
|
elif format.format_type == FormatType.DECIMAL:
|
|
1188
1192
|
return _format_decimal(self._d128, format)
|
|
@@ -1309,7 +1313,7 @@ class Cell(CellStorageFlags, Cacheable):
|
|
|
1309
1313
|
self,
|
|
1310
1314
|
format_id: int,
|
|
1311
1315
|
format_type: Union[FormattingType, CustomFormattingType],
|
|
1312
|
-
control_id: int = None,
|
|
1316
|
+
control_id: Optional[int] = None,
|
|
1313
1317
|
is_currency: bool = False,
|
|
1314
1318
|
) -> None:
|
|
1315
1319
|
self._is_currency = is_currency
|
|
@@ -1339,12 +1343,11 @@ class Cell(CellStorageFlags, Cacheable):
|
|
|
1339
1343
|
|
|
1340
1344
|
|
|
1341
1345
|
class NumberCell(Cell):
|
|
1342
|
-
"""
|
|
1343
|
-
|
|
1344
|
-
Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
|
|
1346
|
+
""".. NOTE::
|
|
1347
|
+
Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
|
|
1345
1348
|
"""
|
|
1346
1349
|
|
|
1347
|
-
def __init__(self, row: int, col: int, value: float, cell_type=CellType.NUMBER):
|
|
1350
|
+
def __init__(self, row: int, col: int, value: float, cell_type=CellType.NUMBER) -> None:
|
|
1348
1351
|
self._type = cell_type
|
|
1349
1352
|
super().__init__(row, col, value)
|
|
1350
1353
|
|
|
@@ -1354,7 +1357,7 @@ class NumberCell(Cell):
|
|
|
1354
1357
|
|
|
1355
1358
|
|
|
1356
1359
|
class TextCell(Cell):
|
|
1357
|
-
def __init__(self, row: int, col: int, value: str):
|
|
1360
|
+
def __init__(self, row: int, col: int, value: str) -> None:
|
|
1358
1361
|
self._type = CellType.TEXT
|
|
1359
1362
|
super().__init__(row, col, value)
|
|
1360
1363
|
|
|
@@ -1364,12 +1367,11 @@ class TextCell(Cell):
|
|
|
1364
1367
|
|
|
1365
1368
|
|
|
1366
1369
|
class RichTextCell(Cell):
|
|
1367
|
-
"""
|
|
1368
|
-
|
|
1369
|
-
Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
|
|
1370
|
+
""".. NOTE::
|
|
1371
|
+
Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
|
|
1370
1372
|
"""
|
|
1371
1373
|
|
|
1372
|
-
def __init__(self, row: int, col: int, value):
|
|
1374
|
+
def __init__(self, row: int, col: int, value) -> None:
|
|
1373
1375
|
super().__init__(row, col, value["text"])
|
|
1374
1376
|
self._type = CellType.RICH_TEXT
|
|
1375
1377
|
self._bullets = value["bullets"]
|
|
@@ -1401,8 +1403,7 @@ class RichTextCell(Cell):
|
|
|
1401
1403
|
|
|
1402
1404
|
@property
|
|
1403
1405
|
def hyperlinks(self) -> Union[List[Tuple], None]:
|
|
1404
|
-
"""
|
|
1405
|
-
List[Tuple] | None: the hyperlinks in a cell or ``None``
|
|
1406
|
+
"""List[Tuple] | None: the hyperlinks in a cell or ``None``.
|
|
1406
1407
|
|
|
1407
1408
|
Numbers does not support hyperlinks to cells within a spreadsheet, but does
|
|
1408
1409
|
allow embedding links in cells. When cells contain hyperlinks,
|
|
@@ -1410,9 +1411,8 @@ class RichTextCell(Cell):
|
|
|
1410
1411
|
of cells where :py:attr:`numbers_parser.Cell.is_bulleted` is ``True`` is a
|
|
1411
1412
|
list of text and URL tuples.
|
|
1412
1413
|
|
|
1413
|
-
Example
|
|
1414
|
+
Example:
|
|
1414
1415
|
-------
|
|
1415
|
-
|
|
1416
1416
|
.. code-block:: python
|
|
1417
1417
|
|
|
1418
1418
|
cell = table.cell(0, 0)
|
|
@@ -1427,12 +1427,11 @@ class BulletedTextCell(RichTextCell):
|
|
|
1427
1427
|
|
|
1428
1428
|
|
|
1429
1429
|
class EmptyCell(Cell):
|
|
1430
|
-
"""
|
|
1431
|
-
|
|
1432
|
-
Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
|
|
1430
|
+
""".. NOTE::
|
|
1431
|
+
Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
|
|
1433
1432
|
"""
|
|
1434
1433
|
|
|
1435
|
-
def __init__(self, row: int, col: int):
|
|
1434
|
+
def __init__(self, row: int, col: int) -> None:
|
|
1436
1435
|
super().__init__(row, col, None)
|
|
1437
1436
|
self._type = CellType.EMPTY
|
|
1438
1437
|
|
|
@@ -1446,12 +1445,11 @@ class EmptyCell(Cell):
|
|
|
1446
1445
|
|
|
1447
1446
|
|
|
1448
1447
|
class BoolCell(Cell):
|
|
1449
|
-
"""
|
|
1450
|
-
|
|
1451
|
-
Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
|
|
1448
|
+
""".. NOTE::
|
|
1449
|
+
Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
|
|
1452
1450
|
"""
|
|
1453
1451
|
|
|
1454
|
-
def __init__(self, row: int, col: int, value: bool):
|
|
1452
|
+
def __init__(self, row: int, col: int, value: bool) -> None:
|
|
1455
1453
|
super().__init__(row, col, value)
|
|
1456
1454
|
self._type = CellType.BOOL
|
|
1457
1455
|
self._value = value
|
|
@@ -1462,12 +1460,11 @@ class BoolCell(Cell):
|
|
|
1462
1460
|
|
|
1463
1461
|
|
|
1464
1462
|
class DateCell(Cell):
|
|
1465
|
-
"""
|
|
1466
|
-
|
|
1467
|
-
Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
|
|
1463
|
+
""".. NOTE::
|
|
1464
|
+
Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
|
|
1468
1465
|
"""
|
|
1469
1466
|
|
|
1470
|
-
def __init__(self, row: int, col: int, value: DateTime):
|
|
1467
|
+
def __init__(self, row: int, col: int, value: DateTime) -> None:
|
|
1471
1468
|
super().__init__(row, col, value)
|
|
1472
1469
|
self._type = CellType.DATE
|
|
1473
1470
|
|
|
@@ -1477,7 +1474,7 @@ class DateCell(Cell):
|
|
|
1477
1474
|
|
|
1478
1475
|
|
|
1479
1476
|
class DurationCell(Cell):
|
|
1480
|
-
def __init__(self, row: int, col: int, value: Duration):
|
|
1477
|
+
def __init__(self, row: int, col: int, value: Duration) -> None:
|
|
1481
1478
|
super().__init__(row, col, value)
|
|
1482
1479
|
self._type = CellType.DURATION
|
|
1483
1480
|
|
|
@@ -1487,12 +1484,11 @@ class DurationCell(Cell):
|
|
|
1487
1484
|
|
|
1488
1485
|
|
|
1489
1486
|
class ErrorCell(Cell):
|
|
1490
|
-
"""
|
|
1491
|
-
|
|
1492
|
-
Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
|
|
1487
|
+
""".. NOTE::
|
|
1488
|
+
Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
|
|
1493
1489
|
"""
|
|
1494
1490
|
|
|
1495
|
-
def __init__(self, row: int, col: int):
|
|
1491
|
+
def __init__(self, row: int, col: int) -> None:
|
|
1496
1492
|
super().__init__(row, col, None)
|
|
1497
1493
|
self._type = CellType.ERROR
|
|
1498
1494
|
|
|
@@ -1502,12 +1498,11 @@ class ErrorCell(Cell):
|
|
|
1502
1498
|
|
|
1503
1499
|
|
|
1504
1500
|
class MergedCell(Cell):
|
|
1505
|
-
"""
|
|
1506
|
-
|
|
1507
|
-
Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
|
|
1501
|
+
""".. NOTE::
|
|
1502
|
+
Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
|
|
1508
1503
|
"""
|
|
1509
1504
|
|
|
1510
|
-
def __init__(self, row: int, col: int):
|
|
1505
|
+
def __init__(self, row: int, col: int) -> None:
|
|
1511
1506
|
super().__init__(row, col, None)
|
|
1512
1507
|
self._type = CellType.MERGED
|
|
1513
1508
|
|
|
@@ -1648,7 +1643,7 @@ def _decode_number_format(format, value, name): # noqa: PLR0912
|
|
|
1648
1643
|
if format.currency_code != "":
|
|
1649
1644
|
# Replace currency code with symbol and no-break space
|
|
1650
1645
|
custom_format_string = custom_format_string.replace(
|
|
1651
|
-
"\u00a4", format.currency_code + "\u00a0"
|
|
1646
|
+
"\u00a4", format.currency_code + "\u00a0",
|
|
1652
1647
|
)
|
|
1653
1648
|
|
|
1654
1649
|
if (match := re.search(r"([#0.,]+(E[+]\d+)?)", custom_format_string)) is None:
|
|
@@ -1799,7 +1794,7 @@ def _format_decimal(value: float, format, percent: bool = False) -> str:
|
|
|
1799
1794
|
else:
|
|
1800
1795
|
formatted_value = sigfig.round(value, MAX_SIGNIFICANT_DIGITS, type=str, warn=False)
|
|
1801
1796
|
formatted_value = sigfig.round(
|
|
1802
|
-
formatted_value, decimals=format.decimal_places, type=str
|
|
1797
|
+
formatted_value, decimals=format.decimal_places, type=str,
|
|
1803
1798
|
)
|
|
1804
1799
|
if format.show_thousands_separator:
|
|
1805
1800
|
formatted_value = sigfig.round(formatted_value, spacer=",", spacing=3, type=str)
|
|
@@ -1832,16 +1827,16 @@ def _format_currency(value: float, format) -> str:
|
|
|
1832
1827
|
return symbol + formatted_value
|
|
1833
1828
|
|
|
1834
1829
|
|
|
1835
|
-
INT_TO_BASE_CHAR = [str(x) for x in range(
|
|
1830
|
+
INT_TO_BASE_CHAR = [str(x) for x in range(10)] + [chr(x) for x in range(ord("A"), ord("Z") + 1)]
|
|
1836
1831
|
|
|
1837
1832
|
|
|
1838
1833
|
def _invert_bit_str(value: str) -> str:
|
|
1839
|
-
"""Invert a binary value"""
|
|
1834
|
+
"""Invert a binary value."""
|
|
1840
1835
|
return "".join(["0" if b == "1" else "1" for b in value])
|
|
1841
1836
|
|
|
1842
1837
|
|
|
1843
1838
|
def _twos_complement(value: int, base: int) -> str:
|
|
1844
|
-
"""Calculate the twos complement of a negative integer with minimum 32-bit precision"""
|
|
1839
|
+
"""Calculate the twos complement of a negative integer with minimum 32-bit precision."""
|
|
1845
1840
|
num_bits = max([32, math.ceil(math.log2(abs(value))) + 1])
|
|
1846
1841
|
bin_value = bin(abs(value))[2:]
|
|
1847
1842
|
inverted_bin_value = _invert_bit_str(bin_value).rjust(num_bits, "1")
|
|
@@ -1930,7 +1925,7 @@ def _format_scientific(value: float, format) -> str:
|
|
|
1930
1925
|
return f"{formatted_value:.{format.decimal_places}E}"
|
|
1931
1926
|
|
|
1932
1927
|
|
|
1933
|
-
def _unit_format(unit: str, value: int, style: int, abbrev: str = None):
|
|
1928
|
+
def _unit_format(unit: str, value: int, style: int, abbrev: Optional[str] = None):
|
|
1934
1929
|
plural = "" if value == 1 else "s"
|
|
1935
1930
|
if abbrev is None:
|
|
1936
1931
|
abbrev = unit[0]
|
|
@@ -1985,8 +1980,7 @@ range_parts = re.compile(r"(\$?)([A-Z]{1,3})(\$?)(\d+)")
|
|
|
1985
1980
|
|
|
1986
1981
|
|
|
1987
1982
|
def xl_cell_to_rowcol(cell_str: str) -> tuple:
|
|
1988
|
-
"""
|
|
1989
|
-
Convert a cell reference in A1 notation to a zero indexed row and column.
|
|
1983
|
+
"""Convert a cell reference in A1 notation to a zero indexed row and column.
|
|
1990
1984
|
|
|
1991
1985
|
Parameters
|
|
1992
1986
|
----------
|
|
@@ -2003,7 +1997,8 @@ def xl_cell_to_rowcol(cell_str: str) -> tuple:
|
|
|
2003
1997
|
|
|
2004
1998
|
match = range_parts.match(cell_str)
|
|
2005
1999
|
if not match:
|
|
2006
|
-
|
|
2000
|
+
msg = f"invalid cell reference {cell_str}"
|
|
2001
|
+
raise IndexError(msg)
|
|
2007
2002
|
|
|
2008
2003
|
col_str = match.group(2)
|
|
2009
2004
|
row_str = match.group(4)
|
|
@@ -2023,8 +2018,7 @@ def xl_cell_to_rowcol(cell_str: str) -> tuple:
|
|
|
2023
2018
|
|
|
2024
2019
|
|
|
2025
2020
|
def xl_range(first_row, first_col, last_row, last_col):
|
|
2026
|
-
"""
|
|
2027
|
-
Convert zero indexed row and col cell references to a A1:B1 range string.
|
|
2021
|
+
"""Convert zero indexed row and col cell references to a A1:B1 range string.
|
|
2028
2022
|
|
|
2029
2023
|
Parameters
|
|
2030
2024
|
----------
|
|
@@ -2052,8 +2046,7 @@ def xl_range(first_row, first_col, last_row, last_col):
|
|
|
2052
2046
|
|
|
2053
2047
|
|
|
2054
2048
|
def xl_rowcol_to_cell(row, col, row_abs=False, col_abs=False):
|
|
2055
|
-
"""
|
|
2056
|
-
Convert a zero indexed row and column cell reference to a A1 style string.
|
|
2049
|
+
"""Convert a zero indexed row and column cell reference to a A1 style string.
|
|
2057
2050
|
|
|
2058
2051
|
Parameters
|
|
2059
2052
|
----------
|
|
@@ -2072,10 +2065,12 @@ def xl_rowcol_to_cell(row, col, row_abs=False, col_abs=False):
|
|
|
2072
2065
|
A1 style string.
|
|
2073
2066
|
"""
|
|
2074
2067
|
if row < 0:
|
|
2075
|
-
|
|
2068
|
+
msg = f"row reference {row} below zero"
|
|
2069
|
+
raise IndexError(msg)
|
|
2076
2070
|
|
|
2077
2071
|
if col < 0:
|
|
2078
|
-
|
|
2072
|
+
msg = f"column reference {col} below zero"
|
|
2073
|
+
raise IndexError(msg)
|
|
2079
2074
|
|
|
2080
2075
|
row += 1 # Change to 1-index.
|
|
2081
2076
|
row_abs = "$" if row_abs else ""
|
|
@@ -2086,8 +2081,7 @@ def xl_rowcol_to_cell(row, col, row_abs=False, col_abs=False):
|
|
|
2086
2081
|
|
|
2087
2082
|
|
|
2088
2083
|
def xl_col_to_name(col, col_abs=False):
|
|
2089
|
-
"""
|
|
2090
|
-
Convert a zero indexed column cell reference to a string.
|
|
2084
|
+
"""Convert a zero indexed column cell reference to a string.
|
|
2091
2085
|
|
|
2092
2086
|
Parameters
|
|
2093
2087
|
----------
|
|
@@ -2095,12 +2089,15 @@ def xl_col_to_name(col, col_abs=False):
|
|
|
2095
2089
|
The column number (zero indexed).
|
|
2096
2090
|
col_abs: bool, default: False
|
|
2097
2091
|
If ``True``, make the column absolute.
|
|
2098
|
-
|
|
2092
|
+
|
|
2093
|
+
Returns
|
|
2094
|
+
-------
|
|
2099
2095
|
str:
|
|
2100
2096
|
Column in A1 notation.
|
|
2101
2097
|
"""
|
|
2102
2098
|
if col < 0:
|
|
2103
|
-
|
|
2099
|
+
msg = f"column reference {col} below zero"
|
|
2100
|
+
raise IndexError(msg)
|
|
2104
2101
|
|
|
2105
2102
|
col += 1 # Change to 1-index.
|
|
2106
2103
|
col_str = ""
|
|
@@ -2149,7 +2146,8 @@ class Formatting:
|
|
|
2149
2146
|
def __post_init__(self):
|
|
2150
2147
|
if not isinstance(self.type, FormattingType):
|
|
2151
2148
|
type_name = type(self.type).__name__
|
|
2152
|
-
|
|
2149
|
+
msg = f"Invalid format type '{type_name}'"
|
|
2150
|
+
raise TypeError(msg)
|
|
2153
2151
|
|
|
2154
2152
|
if self.use_accounting_style and self.negative_style != NegativeNumberStyle.MINUS:
|
|
2155
2153
|
warn(
|
|
@@ -2161,12 +2159,12 @@ class Formatting:
|
|
|
2161
2159
|
if self.type == FormattingType.DATETIME:
|
|
2162
2160
|
formats = re.sub(r"[^a-zA-Z\s]", " ", self.date_time_format).split()
|
|
2163
2161
|
for el in formats:
|
|
2164
|
-
if el not in DATETIME_FIELD_MAP
|
|
2165
|
-
|
|
2162
|
+
if el not in DATETIME_FIELD_MAP:
|
|
2163
|
+
msg = f"Invalid format specifier '{el}' in date/time format"
|
|
2164
|
+
raise TypeError(msg)
|
|
2166
2165
|
|
|
2167
|
-
if self.type == FormattingType.CURRENCY:
|
|
2168
|
-
|
|
2169
|
-
raise TypeError(f"Unsupported currency code '{self.currency_code}'")
|
|
2166
|
+
if self.type == FormattingType.CURRENCY and self.currency_code not in CURRENCIES:
|
|
2167
|
+
raise TypeError(f"Unsupported currency code '{self.currency_code}'")
|
|
2170
2168
|
|
|
2171
2169
|
if self.decimal_places is None:
|
|
2172
2170
|
if self.type == FormattingType.CURRENCY:
|
|
@@ -2179,10 +2177,12 @@ class Formatting:
|
|
|
2179
2177
|
and not self.base_use_minus_sign
|
|
2180
2178
|
and self.base not in (2, 8, 16)
|
|
2181
2179
|
):
|
|
2182
|
-
|
|
2180
|
+
msg = f"base_use_minus_sign must be True for base {self.base}"
|
|
2181
|
+
raise TypeError(msg)
|
|
2183
2182
|
|
|
2184
2183
|
if self.type == FormattingType.BASE and (self.base < 2 or self.base > MAX_BASE):
|
|
2185
|
-
|
|
2184
|
+
msg = "base must be in range 2-36"
|
|
2185
|
+
raise TypeError(msg)
|
|
2186
2186
|
|
|
2187
2187
|
|
|
2188
2188
|
@dataclass
|
|
@@ -2199,11 +2199,11 @@ class CustomFormatting:
|
|
|
2199
2199
|
def __post_init__(self):
|
|
2200
2200
|
if not isinstance(self.type, CustomFormattingType):
|
|
2201
2201
|
type_name = type(self.type).__name__
|
|
2202
|
-
|
|
2202
|
+
msg = f"Invalid format type '{type_name}'"
|
|
2203
|
+
raise TypeError(msg)
|
|
2203
2204
|
|
|
2204
|
-
if self.type == CustomFormattingType.TEXT:
|
|
2205
|
-
|
|
2206
|
-
raise TypeError("Custom formats only allow one text substitution")
|
|
2205
|
+
if self.type == CustomFormattingType.TEXT and self.format.count("%s") > 1:
|
|
2206
|
+
raise TypeError("Custom formats only allow one text substitution")
|
|
2207
2207
|
|
|
2208
2208
|
@classmethod
|
|
2209
2209
|
def from_archive(cls, archive: object):
|