numbers-parser 4.8.1__py3-none-any.whl → 4.9.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/cell.py +80 -13
- numbers_parser/cell_storage.py +48 -12
- numbers_parser/constants.py +103 -6
- numbers_parser/containers.py +2 -2
- numbers_parser/document.py +193 -69
- numbers_parser/file.py +9 -4
- numbers_parser/model.py +104 -1
- {numbers_parser-4.8.1.dist-info → numbers_parser-4.9.0.dist-info}/METADATA +23 -28
- {numbers_parser-4.8.1.dist-info → numbers_parser-4.9.0.dist-info}/RECORD +12 -12
- {numbers_parser-4.8.1.dist-info → numbers_parser-4.9.0.dist-info}/LICENSE.rst +0 -0
- {numbers_parser-4.8.1.dist-info → numbers_parser-4.9.0.dist-info}/WHEEL +0 -0
- {numbers_parser-4.8.1.dist-info → numbers_parser-4.9.0.dist-info}/entry_points.txt +0 -0
numbers_parser/cell.py
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import re
|
|
2
2
|
from collections import namedtuple
|
|
3
|
-
from dataclasses import dataclass
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
4
|
from datetime import datetime as builtin_datetime
|
|
5
5
|
from datetime import timedelta as builtin_timedelta
|
|
6
6
|
from enum import IntEnum
|
|
7
|
+
from os.path import basename
|
|
7
8
|
from typing import Any, List, Tuple, Union
|
|
8
9
|
from warnings import warn
|
|
9
10
|
|
|
@@ -27,6 +28,7 @@ from numbers_parser.constants import (
|
|
|
27
28
|
EMPTY_STORAGE_BUFFER,
|
|
28
29
|
MAX_BASE,
|
|
29
30
|
MAX_SIGNIFICANT_DIGITS,
|
|
31
|
+
ControlFormattingType,
|
|
30
32
|
CustomFormattingType,
|
|
31
33
|
FormattingType,
|
|
32
34
|
FormatType,
|
|
@@ -75,18 +77,43 @@ __all__ = [
|
|
|
75
77
|
|
|
76
78
|
|
|
77
79
|
class BackgroundImage:
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
"""
|
|
81
|
+
A named document style that can be applied to cells.
|
|
82
|
+
|
|
83
|
+
.. code-block:: python
|
|
84
|
+
|
|
85
|
+
fh = open("cats.png", mode="rb")
|
|
86
|
+
image_data = fh.read()
|
|
87
|
+
cats_bg = doc.add_style(
|
|
88
|
+
name="Cats",
|
|
89
|
+
bg_image=BackgroundImage(image_data, "cats.png")
|
|
90
|
+
)
|
|
91
|
+
table.write(0, 0, "❤️ cats", style=cats_bg)
|
|
92
|
+
|
|
93
|
+
Currently only standard image files and not 'advanced' image fills are
|
|
94
|
+
supported. Tiling and scaling is not reported back and cannot be changed
|
|
95
|
+
when saving new cells.
|
|
96
|
+
|
|
97
|
+
Parameters
|
|
98
|
+
----------
|
|
99
|
+
data: bytes
|
|
100
|
+
Raw image data for a cell background image.
|
|
101
|
+
filename: str
|
|
102
|
+
Path to the image file.
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
def __init__(self, data: bytes = None, filename: str = None):
|
|
106
|
+
self._data = data
|
|
107
|
+
self._filename = basename(filename)
|
|
81
108
|
|
|
82
109
|
@property
|
|
83
110
|
def data(self) -> bytes:
|
|
84
|
-
"""The background image as
|
|
111
|
+
"""bytes: The background image as bytes for a cell, or None if no image."""
|
|
85
112
|
return self._data
|
|
86
113
|
|
|
87
114
|
@property
|
|
88
115
|
def filename(self) -> str:
|
|
89
|
-
"""The image filename for a cell, or None if no image."""
|
|
116
|
+
"""str: The image filename for a cell, or None if no image."""
|
|
90
117
|
return self._filename
|
|
91
118
|
|
|
92
119
|
|
|
@@ -184,6 +211,8 @@ class Style:
|
|
|
184
211
|
------
|
|
185
212
|
TypeError:
|
|
186
213
|
If arguments do not match the specified type or for objects have invalid arguments
|
|
214
|
+
IndexError:
|
|
215
|
+
If an image filename already exists in document
|
|
187
216
|
"""
|
|
188
217
|
|
|
189
218
|
alignment: Alignment = DEFAULT_ALIGNMENT_CLASS # : horizontal and vertical alignment
|
|
@@ -230,6 +259,7 @@ class Style:
|
|
|
230
259
|
return [
|
|
231
260
|
"alignment",
|
|
232
261
|
"bg_color",
|
|
262
|
+
"bg_image",
|
|
233
263
|
"first_indent",
|
|
234
264
|
"left_indent",
|
|
235
265
|
"right_indent",
|
|
@@ -273,9 +303,9 @@ class Style:
|
|
|
273
303
|
if not isinstance(self.font_name, str):
|
|
274
304
|
raise TypeError("font name must be a string")
|
|
275
305
|
|
|
276
|
-
for
|
|
277
|
-
if not isinstance(getattr(self,
|
|
278
|
-
raise TypeError(f"{
|
|
306
|
+
for attr in ["bold", "italic", "underline", "strikethrough"]:
|
|
307
|
+
if not isinstance(getattr(self, attr), bool):
|
|
308
|
+
raise TypeError(f"{attr} argument must be boolean")
|
|
279
309
|
|
|
280
310
|
def __setattr__(self, name: str, value: Any) -> None:
|
|
281
311
|
"""Detect changes to cell styles and flag the style for
|
|
@@ -575,8 +605,14 @@ class Cell(Cacheable):
|
|
|
575
605
|
else:
|
|
576
606
|
raise ValueError("Can't determine cell type from type " + type(value).__name__)
|
|
577
607
|
|
|
578
|
-
def _set_formatting(
|
|
579
|
-
self
|
|
608
|
+
def _set_formatting(
|
|
609
|
+
self,
|
|
610
|
+
format_id: int,
|
|
611
|
+
format_type: FormattingType,
|
|
612
|
+
control_id: int = None,
|
|
613
|
+
is_currency: bool = False,
|
|
614
|
+
) -> None:
|
|
615
|
+
self._storage.set_formatting(format_id, format_type, control_id, is_currency)
|
|
580
616
|
|
|
581
617
|
def __init__(self, row: int, col: int, value):
|
|
582
618
|
self._value = value
|
|
@@ -705,7 +741,32 @@ class Cell(Cacheable):
|
|
|
705
741
|
|
|
706
742
|
@property
|
|
707
743
|
def formatted_value(self) -> str:
|
|
708
|
-
"""
|
|
744
|
+
"""
|
|
745
|
+
str: The formatted value of the cell as it appears in Numbers.
|
|
746
|
+
|
|
747
|
+
Interactive elements are converted into a suitable text format where
|
|
748
|
+
supported, or as their number values where there is no suitable
|
|
749
|
+
visual representation. Currently supported mappings are:
|
|
750
|
+
|
|
751
|
+
* Checkboxes are U+2610 (Ballow Box) or U+2611 (Ballot Box with Check)
|
|
752
|
+
* Ratings are their star value represented using (U+2605) (Black Star)
|
|
753
|
+
|
|
754
|
+
.. code-block:: python
|
|
755
|
+
|
|
756
|
+
>>> table = doc.sheets[0].tables[0]
|
|
757
|
+
>>> table.cell(0,0).value
|
|
758
|
+
False
|
|
759
|
+
>>> table.cell(0,0).formatted_value
|
|
760
|
+
'☐'
|
|
761
|
+
>>> table.cell(0,1).value
|
|
762
|
+
True
|
|
763
|
+
>>> table.cell(0,1).formatted_value
|
|
764
|
+
'☑'
|
|
765
|
+
>>> table.cell(1,1).value
|
|
766
|
+
3.0
|
|
767
|
+
>>> table.cell(1,1).formatted_value
|
|
768
|
+
'★★★'
|
|
769
|
+
"""
|
|
709
770
|
if self._storage is None:
|
|
710
771
|
return ""
|
|
711
772
|
else:
|
|
@@ -1079,16 +1140,22 @@ def xl_col_to_name(col, col_abs=False):
|
|
|
1079
1140
|
|
|
1080
1141
|
@dataclass()
|
|
1081
1142
|
class Formatting:
|
|
1082
|
-
|
|
1143
|
+
allow_none: bool = False
|
|
1083
1144
|
base_places: int = 0
|
|
1084
1145
|
base_use_minus_sign: bool = True
|
|
1085
1146
|
base: int = 10
|
|
1147
|
+
control_format: ControlFormattingType = ControlFormattingType.NUMBER
|
|
1086
1148
|
currency_code: str = "GBP"
|
|
1087
1149
|
date_time_format: str = DEFAULT_DATETIME_FORMAT
|
|
1088
1150
|
decimal_places: int = None
|
|
1089
1151
|
fraction_accuracy: FractionAccuracy = FractionAccuracy.THREE
|
|
1152
|
+
increment: float = 1.0
|
|
1153
|
+
maximum: float = 100.0
|
|
1154
|
+
minimum: float = 1.0
|
|
1155
|
+
popup_values: List[str] = field(default_factory=lambda: ["Item 1"])
|
|
1090
1156
|
negative_style: NegativeNumberStyle = NegativeNumberStyle.MINUS
|
|
1091
1157
|
show_thousands_separator: bool = False
|
|
1158
|
+
type: FormattingType = FormattingType.NUMBER
|
|
1092
1159
|
use_accounting_style: bool = False
|
|
1093
1160
|
_format_id = None
|
|
1094
1161
|
|
numbers_parser/cell_storage.py
CHANGED
|
@@ -11,6 +11,8 @@ from pendulum import datetime, duration
|
|
|
11
11
|
|
|
12
12
|
from numbers_parser import __name__ as numbers_parser_name
|
|
13
13
|
from numbers_parser.constants import (
|
|
14
|
+
CHECKBOX_FALSE_VALUE,
|
|
15
|
+
CHECKBOX_TRUE_VALUE,
|
|
14
16
|
CURRENCY_CELL_TYPE,
|
|
15
17
|
CUSTOM_TEXT_PLACEHOLDER,
|
|
16
18
|
DATETIME_FIELD_MAP,
|
|
@@ -21,6 +23,7 @@ from numbers_parser.constants import (
|
|
|
21
23
|
SECONDS_IN_DAY,
|
|
22
24
|
SECONDS_IN_HOUR,
|
|
23
25
|
SECONDS_IN_WEEK,
|
|
26
|
+
STAR_RATING_VALUE,
|
|
24
27
|
CellPadding,
|
|
25
28
|
CellType,
|
|
26
29
|
CustomFormattingType,
|
|
@@ -60,7 +63,7 @@ class CellStorage(Cacheable):
|
|
|
60
63
|
# "cond_style_id",
|
|
61
64
|
# "cond_rule_style_id",
|
|
62
65
|
"formula_id",
|
|
63
|
-
|
|
66
|
+
"control_id",
|
|
64
67
|
"formula_error_id",
|
|
65
68
|
"suggest_id",
|
|
66
69
|
"num_format_id",
|
|
@@ -95,7 +98,7 @@ class CellStorage(Cacheable):
|
|
|
95
98
|
# self.cond_style_id = None
|
|
96
99
|
# self.cond_rule_style_id = None
|
|
97
100
|
self.formula_id = None
|
|
98
|
-
|
|
101
|
+
self.control_id = None
|
|
99
102
|
self.formula_error_id = None
|
|
100
103
|
self.suggest_id = None
|
|
101
104
|
self.num_format_id = None
|
|
@@ -148,9 +151,9 @@ class CellStorage(Cacheable):
|
|
|
148
151
|
if flags & 0x200:
|
|
149
152
|
self.formula_id = unpack("<i", buffer[offset : offset + 4])[0]
|
|
150
153
|
offset += 4
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
+
if flags & 0x400:
|
|
155
|
+
self.control_id = unpack("<i", buffer[offset : offset + 4])[0]
|
|
156
|
+
offset += 4
|
|
154
157
|
# if flags & 0x800:
|
|
155
158
|
# self.formula_error_id = unpack("<i", buffer[offset : offset + 4])[0]
|
|
156
159
|
# offset += 4
|
|
@@ -158,7 +161,7 @@ class CellStorage(Cacheable):
|
|
|
158
161
|
self.suggest_id = unpack("<i", buffer[offset : offset + 4])[0]
|
|
159
162
|
offset += 4
|
|
160
163
|
# Skip unused flags
|
|
161
|
-
offset += 4 * bin(flags &
|
|
164
|
+
offset += 4 * bin(flags & 0x900).count("1")
|
|
162
165
|
#
|
|
163
166
|
if flags & 0x2000:
|
|
164
167
|
self.num_format_id = unpack("<i", buffer[offset : offset + 4])[0]
|
|
@@ -300,13 +303,15 @@ class CellStorage(Cacheable):
|
|
|
300
303
|
format = self.model.table_format(self.table_id, self.text_format_id)
|
|
301
304
|
elif self.currency_format_id is not None:
|
|
302
305
|
format = self.model.table_format(self.table_id, self.currency_format_id)
|
|
306
|
+
elif self.bool_format_id is not None and self.type == CellType.BOOL:
|
|
307
|
+
format = self.model.table_format(self.table_id, self.bool_format_id)
|
|
303
308
|
elif self.num_format_id is not None:
|
|
304
309
|
format = self.model.table_format(self.table_id, self.num_format_id)
|
|
305
|
-
elif self.bool_format_id is not None:
|
|
306
|
-
format = self.model.table_format(self.table_id, self.bool_format_id)
|
|
307
310
|
else:
|
|
308
311
|
return str(self.value)
|
|
309
312
|
|
|
313
|
+
debug("custom_format: @[%d,%d]: format_type=%s, ", self.row, self.col, format.format_type)
|
|
314
|
+
|
|
310
315
|
if format.HasField("custom_uid"):
|
|
311
316
|
format_uuid = NumbersUUID(format.custom_uid).hex
|
|
312
317
|
format_map = self.model.custom_format_map()
|
|
@@ -336,6 +341,10 @@ class CellStorage(Cacheable):
|
|
|
336
341
|
return format_fraction(self.d128, format)
|
|
337
342
|
elif format.format_type == FormatType.SCIENTIFIC:
|
|
338
343
|
return format_scientific(self.d128, format)
|
|
344
|
+
elif format.format_type == FormatType.CHECKBOX:
|
|
345
|
+
return CHECKBOX_TRUE_VALUE if self.value else CHECKBOX_FALSE_VALUE
|
|
346
|
+
elif format.format_type == FormatType.RATING:
|
|
347
|
+
return STAR_RATING_VALUE * int(self.d128)
|
|
339
348
|
else:
|
|
340
349
|
formatted_value = str(self.value)
|
|
341
350
|
return formatted_value
|
|
@@ -362,6 +371,14 @@ class CellStorage(Cacheable):
|
|
|
362
371
|
|
|
363
372
|
def duration_format(self) -> str:
|
|
364
373
|
format = self.model.table_format(self.table_id, self.duration_format_id)
|
|
374
|
+
debug(
|
|
375
|
+
"duration_format: @[%d,%d]: table_id=%d, duration_format_id=%d, duration_style=%s",
|
|
376
|
+
self.row,
|
|
377
|
+
self.col,
|
|
378
|
+
self.table_id,
|
|
379
|
+
self.duration_format_id,
|
|
380
|
+
format.duration_style,
|
|
381
|
+
)
|
|
365
382
|
|
|
366
383
|
duration_style = format.duration_style
|
|
367
384
|
unit_largest = format.duration_unit_largest
|
|
@@ -431,15 +448,34 @@ class CellStorage(Cacheable):
|
|
|
431
448
|
|
|
432
449
|
return duration_str
|
|
433
450
|
|
|
434
|
-
def
|
|
435
|
-
self,
|
|
451
|
+
def set_formatting(
|
|
452
|
+
self,
|
|
453
|
+
format_id: int,
|
|
454
|
+
format_type: Union[FormattingType, CustomFormattingType],
|
|
455
|
+
control_id: int = None,
|
|
456
|
+
is_currency: bool = False,
|
|
436
457
|
) -> None:
|
|
458
|
+
self.is_currency = is_currency
|
|
437
459
|
if format_type == FormattingType.CURRENCY:
|
|
438
460
|
self.currency_format_id = format_id
|
|
439
|
-
|
|
461
|
+
elif format_type == FormattingType.TICKBOX:
|
|
462
|
+
self.bool_format_id = format_id
|
|
463
|
+
self.control_id = control_id
|
|
464
|
+
elif format_type == FormattingType.RATING:
|
|
465
|
+
self.num_format_id = format_id
|
|
466
|
+
self.control_id = control_id
|
|
467
|
+
elif format_type in [FormattingType.SLIDER, FormattingType.STEPPER]:
|
|
468
|
+
if is_currency:
|
|
469
|
+
self.currency_format_id = format_id
|
|
470
|
+
else:
|
|
471
|
+
self.num_format_id = format_id
|
|
472
|
+
self.control_id = control_id
|
|
473
|
+
elif format_type == FormattingType.POPUP:
|
|
474
|
+
self.text_format_id = format_id
|
|
475
|
+
self.control_id = control_id
|
|
440
476
|
elif format_type in [FormattingType.DATETIME, CustomFormattingType.DATETIME]:
|
|
441
477
|
self.date_format_id = format_id
|
|
442
|
-
elif format_type
|
|
478
|
+
elif format_type in [FormattingType.TEXT, CustomFormattingType.TEXT]:
|
|
443
479
|
self.text_format_id = format_id
|
|
444
480
|
else:
|
|
445
481
|
self.num_format_id = format_id
|
numbers_parser/constants.py
CHANGED
|
@@ -20,6 +20,7 @@ __all__ = [
|
|
|
20
20
|
"FormattingType",
|
|
21
21
|
"NegativeNumberStyle",
|
|
22
22
|
"FractionAccuracy",
|
|
23
|
+
"ControlFormattingType",
|
|
23
24
|
]
|
|
24
25
|
|
|
25
26
|
DEFAULT_DOCUMENT = files("numbers_parser") / "data" / "empty.numbers"
|
|
@@ -44,8 +45,11 @@ DEFAULT_TEXT_INSET = 4.0
|
|
|
44
45
|
DEFAULT_TEXT_WRAP = True
|
|
45
46
|
EMPTY_STORAGE_BUFFER = b"\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
46
47
|
|
|
47
|
-
# Formatting defaults
|
|
48
|
+
# Formatting values and defaults
|
|
48
49
|
DEFAULT_DATETIME_FORMAT = "dd MMM YYY HH:MM"
|
|
50
|
+
CHECKBOX_FALSE_VALUE = "☐"
|
|
51
|
+
CHECKBOX_TRUE_VALUE = "☑"
|
|
52
|
+
STAR_RATING_VALUE = "★"
|
|
49
53
|
|
|
50
54
|
# Numbers limits
|
|
51
55
|
MAX_TILE_SIZE = 256
|
|
@@ -179,6 +183,39 @@ class FormattingType(IntEnum):
|
|
|
179
183
|
NUMBER = 5
|
|
180
184
|
PERCENTAGE = 6
|
|
181
185
|
SCIENTIFIC = 7
|
|
186
|
+
TICKBOX = 8
|
|
187
|
+
RATING = 9
|
|
188
|
+
SLIDER = 10
|
|
189
|
+
STEPPER = 11
|
|
190
|
+
POPUP = 12
|
|
191
|
+
TEXT = 13
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class ControlFormattingType(IntEnum):
|
|
195
|
+
BASE = 1
|
|
196
|
+
CURRENCY = 2
|
|
197
|
+
FRACTION = 4
|
|
198
|
+
NUMBER = 5
|
|
199
|
+
PERCENTAGE = 6
|
|
200
|
+
SCIENTIFIC = 7
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
FORMATTING_ALLOWED_CELLS = {
|
|
204
|
+
"base": ["NumberCell"],
|
|
205
|
+
"currency": ["NumberCell"],
|
|
206
|
+
"datetime": ["DateCell"],
|
|
207
|
+
"fraction": ["NumberCell"],
|
|
208
|
+
"number": ["NumberCell"],
|
|
209
|
+
"percentage": ["NumberCell"],
|
|
210
|
+
"popup": ["NumberCell", "TextCell"],
|
|
211
|
+
"rating": ["NumberCell"],
|
|
212
|
+
"scientific": ["NumberCell"],
|
|
213
|
+
"slider": ["NumberCell"],
|
|
214
|
+
"stepper": ["NumberCell"],
|
|
215
|
+
"tickbox": ["BoolCell"],
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
FORMATTING_ACTION_CELLS = ["tickbox", "rating", "popup", "slider", "stepper"]
|
|
182
219
|
|
|
183
220
|
|
|
184
221
|
class CustomFormattingType(IntEnum):
|
|
@@ -187,6 +224,13 @@ class CustomFormattingType(IntEnum):
|
|
|
187
224
|
TEXT = 103
|
|
188
225
|
|
|
189
226
|
|
|
227
|
+
CUSTOM_FORMATTING_ALLOWED_CELLS = {
|
|
228
|
+
"number": ["NumberCell"],
|
|
229
|
+
"datetime": ["DateCell"],
|
|
230
|
+
"text": ["TextCell"],
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
|
|
190
234
|
@enum_tools.documentation.document_enum
|
|
191
235
|
class NegativeNumberStyle(IntEnum):
|
|
192
236
|
"""
|
|
@@ -236,19 +280,47 @@ class FractionAccuracy(IntEnum):
|
|
|
236
280
|
|
|
237
281
|
|
|
238
282
|
ALLOWED_FORMATTING_PARAMETERS = {
|
|
239
|
-
FormattingType.BASE: [
|
|
283
|
+
FormattingType.BASE: [
|
|
284
|
+
"base",
|
|
285
|
+
"base_places",
|
|
286
|
+
"base_use_minus_sign",
|
|
287
|
+
],
|
|
240
288
|
FormattingType.CURRENCY: [
|
|
289
|
+
"currency_code",
|
|
241
290
|
"decimal_places",
|
|
242
|
-
"show_thousands_separator",
|
|
243
291
|
"negative_style",
|
|
292
|
+
"show_thousands_separator",
|
|
244
293
|
"use_accounting_style",
|
|
245
|
-
"currency_code",
|
|
246
294
|
],
|
|
247
295
|
FormattingType.DATETIME: ["date_time_format"],
|
|
248
296
|
FormattingType.FRACTION: ["fraction_accuracy"],
|
|
249
|
-
FormattingType.NUMBER: [
|
|
250
|
-
|
|
297
|
+
FormattingType.NUMBER: [
|
|
298
|
+
"decimal_places",
|
|
299
|
+
"show_thousands_separator",
|
|
300
|
+
"negative_style",
|
|
301
|
+
],
|
|
302
|
+
FormattingType.PERCENTAGE: [
|
|
303
|
+
"decimal_places",
|
|
304
|
+
"show_thousands_separator",
|
|
305
|
+
"negative_style",
|
|
306
|
+
],
|
|
251
307
|
FormattingType.SCIENTIFIC: ["decimal_places"],
|
|
308
|
+
FormattingType.POPUP: ["popup_values", "allow_none"],
|
|
309
|
+
FormattingType.RATING: [],
|
|
310
|
+
FormattingType.SLIDER: [
|
|
311
|
+
"control_format",
|
|
312
|
+
"increment",
|
|
313
|
+
"maximum",
|
|
314
|
+
"minimum",
|
|
315
|
+
],
|
|
316
|
+
FormattingType.STEPPER: [
|
|
317
|
+
"control_format",
|
|
318
|
+
"increment",
|
|
319
|
+
"maximum",
|
|
320
|
+
"minimum",
|
|
321
|
+
],
|
|
322
|
+
FormattingType.TICKBOX: [],
|
|
323
|
+
FormattingType.TEXT: [],
|
|
252
324
|
}
|
|
253
325
|
|
|
254
326
|
FORMAT_TYPE_MAP = {
|
|
@@ -258,7 +330,13 @@ FORMAT_TYPE_MAP = {
|
|
|
258
330
|
FormattingType.FRACTION: FormatType.FRACTION,
|
|
259
331
|
FormattingType.NUMBER: FormatType.DECIMAL,
|
|
260
332
|
FormattingType.PERCENTAGE: FormatType.PERCENT,
|
|
333
|
+
FormattingType.POPUP: FormatType.TEXT,
|
|
334
|
+
FormattingType.RATING: FormatType.RATING,
|
|
261
335
|
FormattingType.SCIENTIFIC: FormatType.SCIENTIFIC,
|
|
336
|
+
FormattingType.SLIDER: FormatType.DECIMAL,
|
|
337
|
+
FormattingType.STEPPER: FormatType.DECIMAL,
|
|
338
|
+
FormattingType.TICKBOX: FormatType.CHECKBOX,
|
|
339
|
+
FormattingType.TEXT: FormatType.TEXT,
|
|
262
340
|
}
|
|
263
341
|
|
|
264
342
|
CUSTOM_FORMAT_TYPE_MAP = {
|
|
@@ -268,6 +346,25 @@ CUSTOM_FORMAT_TYPE_MAP = {
|
|
|
268
346
|
}
|
|
269
347
|
|
|
270
348
|
|
|
349
|
+
class CellInteractionType(IntEnum):
|
|
350
|
+
VALUE_EDITING = 0
|
|
351
|
+
FORMULA_EDITING = 1
|
|
352
|
+
STOCK = 2
|
|
353
|
+
CATEGORY_SUMMARY = 3
|
|
354
|
+
STEPPER = 4
|
|
355
|
+
SLIDER = 5
|
|
356
|
+
RATING = 6
|
|
357
|
+
POPUP = 7
|
|
358
|
+
TOGGLE = 8
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
CONTROL_CELL_TYPE_MAP = {
|
|
362
|
+
FormattingType.POPUP: CellInteractionType.POPUP,
|
|
363
|
+
FormattingType.SLIDER: CellInteractionType.SLIDER,
|
|
364
|
+
FormattingType.STEPPER: CellInteractionType.STEPPER,
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
|
|
271
368
|
@enum_tools.documentation.document_enum
|
|
272
369
|
class PaddingType(IntEnum):
|
|
273
370
|
"""How integers and decimals are padded in custom number formats"""
|
numbers_parser/containers.py
CHANGED
|
@@ -59,7 +59,7 @@ class ObjectStore:
|
|
|
59
59
|
self._objects[PACKAGE_ID].last_object_identifier = self._max_id
|
|
60
60
|
return self._max_id
|
|
61
61
|
|
|
62
|
-
def create_object_from_dict(self, iwa_file: str, object_dict: dict, cls: object):
|
|
62
|
+
def create_object_from_dict(self, iwa_file: str, object_dict: dict, cls: object, append=False):
|
|
63
63
|
"""Create a new object and store the associated IWA segment. Return the
|
|
64
64
|
message ID for the object and the newly created object. If the IWA
|
|
65
65
|
file cannot be found, it will be created.
|
|
@@ -70,7 +70,7 @@ class ObjectStore:
|
|
|
70
70
|
new_id = self.new_message_id()
|
|
71
71
|
iwa_segment = create_iwa_segment(new_id, cls, object_dict)
|
|
72
72
|
|
|
73
|
-
if iwa_pathname is None:
|
|
73
|
+
if iwa_pathname is None and not append:
|
|
74
74
|
iwa_pathname = iwa_file.format(new_id) + ".iwa"
|
|
75
75
|
chunks = {"chunks": [{"archives": [iwa_segment.to_dict()]}]}
|
|
76
76
|
self._file_store[iwa_pathname] = IWAFile.from_dict(chunks)
|
numbers_parser/document.py
CHANGED
|
@@ -4,15 +4,15 @@ from warnings import warn
|
|
|
4
4
|
from pendulum import DateTime, Duration
|
|
5
5
|
|
|
6
6
|
from numbers_parser.cell import (
|
|
7
|
+
BackgroundImage,
|
|
7
8
|
Border,
|
|
8
9
|
Cell,
|
|
10
|
+
ControlFormattingType,
|
|
9
11
|
CustomFormatting,
|
|
10
12
|
CustomFormattingType,
|
|
11
|
-
DateCell,
|
|
12
13
|
Formatting,
|
|
13
14
|
FormattingType,
|
|
14
15
|
MergedCell,
|
|
15
|
-
NumberCell,
|
|
16
16
|
Style,
|
|
17
17
|
TextCell,
|
|
18
18
|
UnsupportedWarning,
|
|
@@ -21,8 +21,11 @@ from numbers_parser.cell import (
|
|
|
21
21
|
)
|
|
22
22
|
from numbers_parser.cell_storage import CellStorage
|
|
23
23
|
from numbers_parser.constants import (
|
|
24
|
+
CUSTOM_FORMATTING_ALLOWED_CELLS,
|
|
24
25
|
DEFAULT_COLUMN_COUNT,
|
|
25
26
|
DEFAULT_ROW_COUNT,
|
|
27
|
+
FORMATTING_ACTION_CELLS,
|
|
28
|
+
FORMATTING_ALLOWED_CELLS,
|
|
26
29
|
MAX_COL_COUNT,
|
|
27
30
|
MAX_HEADER_COUNT,
|
|
28
31
|
MAX_ROW_COUNT,
|
|
@@ -221,10 +224,17 @@ class Document:
|
|
|
221
224
|
------
|
|
222
225
|
TypeError:
|
|
223
226
|
If ``font_size`` is not a ``float``, ``font_name`` is not a ``str``,
|
|
227
|
+
``bg_image`` is not a :py:class:`~numbers_parser.BackgroundImage`,
|
|
224
228
|
or if any of the ``bool`` parameters are invalid.
|
|
225
229
|
""" # noqa: E501
|
|
226
230
|
if "name" in kwargs and kwargs["name"] is not None and kwargs["name"] in self._model.styles:
|
|
227
231
|
raise IndexError(f"style '{kwargs['name']}' already exists")
|
|
232
|
+
|
|
233
|
+
if "bg_image" in kwargs and kwargs["bg_image"] is not None:
|
|
234
|
+
if not isinstance(kwargs["bg_image"], BackgroundImage):
|
|
235
|
+
raise TypeError("bg_image must be a BackgroundImage object")
|
|
236
|
+
self._model.store_image((kwargs["bg_image"].data), kwargs["bg_image"].filename)
|
|
237
|
+
|
|
228
238
|
style = Style(**kwargs)
|
|
229
239
|
if style.name is None:
|
|
230
240
|
style.name = self._model.custom_style_name()
|
|
@@ -814,20 +824,28 @@ class Table(Cacheable): # noqa: F811
|
|
|
814
824
|
The ``write()`` method supports two forms of notation to designate the position
|
|
815
825
|
of cells: **Row-column** notation and **A1** notation:
|
|
816
826
|
|
|
817
|
-
.. code
|
|
827
|
+
.. code:: python
|
|
818
828
|
|
|
819
|
-
|
|
820
|
-
|
|
829
|
+
doc = Document("write.numbers")
|
|
830
|
+
sheets = doc.sheets
|
|
831
|
+
tables = sheets[0].tables
|
|
832
|
+
table = tables[0]
|
|
833
|
+
table.write(1, 1, "This is new text")
|
|
834
|
+
table.write("B7", datetime(2020, 12, 25))
|
|
835
|
+
doc.save("new-sheet.numbers")
|
|
821
836
|
|
|
822
837
|
Parameters
|
|
823
838
|
----------
|
|
824
839
|
|
|
825
|
-
|
|
840
|
+
row: int
|
|
826
841
|
The row number (zero indexed)
|
|
827
|
-
|
|
842
|
+
col: int
|
|
828
843
|
The column number (zero indexed)
|
|
829
|
-
|
|
830
|
-
The value to write to the cell. The generated cell type
|
|
844
|
+
value: str | int | float | bool | DateTime | Duration
|
|
845
|
+
The value to write to the cell. The generated cell type is automatically
|
|
846
|
+
created based on the type of ``value``.
|
|
847
|
+
style: Style | str | None
|
|
848
|
+
The name of a document custom style or a :py:class:`~numbers_parser.cell.Style` object.
|
|
831
849
|
|
|
832
850
|
Warns
|
|
833
851
|
-----
|
|
@@ -843,19 +861,6 @@ class Table(Cacheable): # noqa: F811
|
|
|
843
861
|
If the style parameter is an invalid type.
|
|
844
862
|
ValueError:
|
|
845
863
|
If the cell type cannot be determined from the type of `param3`.
|
|
846
|
-
|
|
847
|
-
Example
|
|
848
|
-
-------
|
|
849
|
-
|
|
850
|
-
.. code:: python
|
|
851
|
-
|
|
852
|
-
doc = Document("write.numbers")
|
|
853
|
-
sheets = doc.sheets
|
|
854
|
-
tables = sheets[0].tables
|
|
855
|
-
table = tables[0]
|
|
856
|
-
table.write(1, 1, "This is new text")
|
|
857
|
-
table.write("B7", datetime(2020, 12, 25))
|
|
858
|
-
doc.save("new-sheet.numbers")
|
|
859
864
|
"""
|
|
860
865
|
# TODO: write needs to retain/init the border
|
|
861
866
|
(row, col, value) = self._validate_cell_coords(*args)
|
|
@@ -1103,6 +1108,48 @@ class Table(Cacheable): # noqa: F811
|
|
|
1103
1108
|
cell._set_merge(merge_cells.get((row, col)))
|
|
1104
1109
|
|
|
1105
1110
|
def set_cell_border(self, *args):
|
|
1111
|
+
"""
|
|
1112
|
+
Set the borders for a cell.
|
|
1113
|
+
|
|
1114
|
+
Cell references can be row-column offsers or Excel/Numbers-style A1 notation. Borders
|
|
1115
|
+
can be applied to multiple sides of a cell by passing a list of sides. The name(s)
|
|
1116
|
+
of the side(s) must be one of ``"top"``, ``"right"``, ``"bottom"`` or ``"left"``.
|
|
1117
|
+
|
|
1118
|
+
Numbers supports different border styles for each cell within a merged cell range
|
|
1119
|
+
for those cells that are on the outer part of the merge. ``numbers-parser`` will
|
|
1120
|
+
ignore attempts to set these invisible cell edges and issue a ``RuntimeWarning``.
|
|
1121
|
+
|
|
1122
|
+
.. code-block:: python
|
|
1123
|
+
|
|
1124
|
+
# Dashed line for B7's right border
|
|
1125
|
+
table.set_cell_border(6, 1, "right", Border(5.0, RGB(29, 177, 0), "dashes"))
|
|
1126
|
+
# Solid line starting at B7's left border and running for 3 rows
|
|
1127
|
+
table.set_cell_border("B7", "left", Border(8.0, RGB(29, 177, 0), "solid"), 3)
|
|
1128
|
+
|
|
1129
|
+
:Args (row-column):
|
|
1130
|
+
* **param1** (*int*): The row number (zero indexed).
|
|
1131
|
+
* **param2** (*int*): The column number (zero indexed).
|
|
1132
|
+
* **param3** (*str | List[str]*): Which side(s) of the cell to apply the border to.
|
|
1133
|
+
* **param4** (:py:class:`Border`): The border to add.
|
|
1134
|
+
* **param5** (*int*, *optinal*, default: 1): The length of the stroke to add.
|
|
1135
|
+
|
|
1136
|
+
:Args (A1):
|
|
1137
|
+
* **param1** (*str*): A cell reference using Excel/Numbers-style A1 notation.
|
|
1138
|
+
* **param2** (*str | List[str]*): Which side(s) of the cell to apply the border to.
|
|
1139
|
+
* **param3** (:py:class:`Border`): The border to add.
|
|
1140
|
+
* **param4** (*int*, *optional*, default: 1): The length of the stroke to add.
|
|
1141
|
+
|
|
1142
|
+
Raises
|
|
1143
|
+
------
|
|
1144
|
+
TypeError:
|
|
1145
|
+
If an invalid number of arguments is passed or if the types of the arguments
|
|
1146
|
+
are invalid.
|
|
1147
|
+
|
|
1148
|
+
Warns
|
|
1149
|
+
-----
|
|
1150
|
+
RuntimeWarning:
|
|
1151
|
+
If any of the sides to which the border is applied have been merged.
|
|
1152
|
+
""" # noqa: E501
|
|
1106
1153
|
(row, col, *args) = self._validate_cell_coords(*args)
|
|
1107
1154
|
if len(args) == 2:
|
|
1108
1155
|
(side, border_value) = args
|
|
@@ -1165,32 +1212,52 @@ class Table(Cacheable): # noqa: F811
|
|
|
1165
1212
|
|
|
1166
1213
|
Cell references can be **row-column** offsers or Excel/Numbers-style **A1** notation.
|
|
1167
1214
|
|
|
1215
|
+
.. code:: python
|
|
1216
|
+
|
|
1217
|
+
table.set_cell_formatting(
|
|
1218
|
+
"C1",
|
|
1219
|
+
"date",
|
|
1220
|
+
date_time_format="EEEE, d MMMM yyyy"
|
|
1221
|
+
)
|
|
1222
|
+
table.set_cell_formatting(
|
|
1223
|
+
0,
|
|
1224
|
+
4,
|
|
1225
|
+
"number",
|
|
1226
|
+
decimal_places=3,
|
|
1227
|
+
negative_style=NegativeNumberStyle.RED
|
|
1228
|
+
)
|
|
1229
|
+
|
|
1168
1230
|
:Parameters:
|
|
1169
1231
|
* **args** (*list*, *optional*) – Positional arguments for cell reference and data format type (see below)
|
|
1170
1232
|
* **kwargs** (*dict*, *optional*) - Key-value pairs defining a formatting options for each data format (see below).
|
|
1171
1233
|
|
|
1172
1234
|
:Args (row-column):
|
|
1173
|
-
* **param1** (
|
|
1174
|
-
* **param2** (
|
|
1175
|
-
* **param3** (
|
|
1235
|
+
* **param1** (*int*): The row number (zero indexed).
|
|
1236
|
+
* **param2** (*int*): The column number (zero indexed).
|
|
1237
|
+
* **param3** (*str*): Data format type for the cell (see "data formats" below).
|
|
1176
1238
|
|
|
1177
1239
|
:Args (A1):
|
|
1178
|
-
* **param1** (
|
|
1179
|
-
* **param2** (
|
|
1240
|
+
* **param1** (*str*): A cell reference using Excel/Numbers-style A1 notation.
|
|
1241
|
+
* **param2** (*str*): Data format type for the cell (see "data formats" below).
|
|
1242
|
+
|
|
1243
|
+
:Raises:
|
|
1244
|
+
* **TypeError** -
|
|
1245
|
+
If a tickbox is chosen for anything other than ``bool`` values.
|
|
1180
1246
|
|
|
1181
1247
|
:Warns:
|
|
1182
1248
|
* **RuntimeWarning** -
|
|
1183
1249
|
If ``use_accounting_style`` is used with
|
|
1184
|
-
any ``negative_style`` other than ``NegativeNumberStyle.MINUS
|
|
1250
|
+
any ``negative_style`` other than ``NegativeNumberStyle.MINUS``, or
|
|
1251
|
+
if a rating is out of range 0 to 5 (rating is clamped to these values).
|
|
1185
1252
|
|
|
1186
1253
|
All formatting styles share a name and a type, described in the **Common**
|
|
1187
1254
|
parameters in the following table. Additional key-value pairs configure the format
|
|
1188
1255
|
depending upon the value of ``kwargs["type"]``.
|
|
1189
1256
|
|
|
1190
1257
|
:Common Args:
|
|
1191
|
-
* **name** (
|
|
1258
|
+
* **name** (*str*) – The name of the custom format. If no name is provided,
|
|
1192
1259
|
one is generated using the scheme ``Custom Format``, ``Custom Format 1``, ``Custom Format 2``, etc.
|
|
1193
|
-
* **type** (
|
|
1260
|
+
* **type** (*str, optional, default: number*) – The type of format to
|
|
1194
1261
|
create:
|
|
1195
1262
|
|
|
1196
1263
|
* ``"base"``: A number base in the range 2-36.
|
|
@@ -1200,56 +1267,89 @@ class Table(Cacheable): # noqa: F811
|
|
|
1200
1267
|
* ``"percentage"``: A number formatted as a percentage
|
|
1201
1268
|
* ``"number"``: A decimal number.
|
|
1202
1269
|
* ``"scientific"``: A decimal number with scientific notation.
|
|
1270
|
+
* ``"tickbox"``: A checkbox (bool values only).
|
|
1271
|
+
* ``"rating"``: A star rating from 0 to 5.
|
|
1272
|
+
* ``"slider"``: A range slider.
|
|
1273
|
+
* ``"stepper"``: An up/down value stepper.
|
|
1274
|
+
* ``"popup"``: A menu of options.
|
|
1203
1275
|
|
|
1204
1276
|
:``"base"``:
|
|
1205
|
-
* **base_use_minus_sign** (
|
|
1277
|
+
* **base_use_minus_sign** (*int, optional, default: 10*) – The integer
|
|
1206
1278
|
base to represent the number from 2-36.
|
|
1207
|
-
* **base_use_minus_sign** (
|
|
1279
|
+
* **base_use_minus_sign** (*bool, optional, default: True*) – If ``True``
|
|
1208
1280
|
use a standard minus sign, otherwise format as two's compliment (only
|
|
1209
1281
|
possible for binary, octal and hexadecimal.
|
|
1210
|
-
* **base_places** (
|
|
1282
|
+
* **base_places** (*int, optional, default: 0*) – The number of
|
|
1211
1283
|
decimal places, or ``None`` for automatic.
|
|
1212
1284
|
|
|
1213
1285
|
:``"currency"``:
|
|
1214
|
-
* **currency** (
|
|
1286
|
+
* **currency** (*str, optional, default: "GBP"*) – An ISO currency
|
|
1215
1287
|
code, e.g. ``"GBP"`` or ``"USD"``.
|
|
1216
|
-
* **decimal_places** (
|
|
1288
|
+
* **decimal_places** (*int, optional, default: 2*) – The number of
|
|
1217
1289
|
decimal places, or ``None`` for automatic.
|
|
1218
|
-
* **negative_style** (
|
|
1290
|
+
* **negative_style** (*:py:class:`~numbers_parser.NegativeNumberStyle`, optional, default: NegativeNumberStyle.MINUS*) – How negative numbers are represented.
|
|
1219
1291
|
See `Negative number formats <#negative-formats>`_.
|
|
1220
|
-
* **show_thousands_separator** (
|
|
1292
|
+
* **show_thousands_separator** (*bool, optional, default: False*) – ``True``
|
|
1221
1293
|
if the number should include a thousands seperator, e.g. ``,``
|
|
1222
|
-
* **use_accounting_style** (
|
|
1294
|
+
* **use_accounting_style** (*bool, optional, default: False*) – ``True``
|
|
1223
1295
|
if the currency symbol should be formatted to the left of the cell and
|
|
1224
1296
|
separated from the number value by a tab.
|
|
1225
1297
|
|
|
1226
1298
|
:``"datetime"``:
|
|
1227
|
-
* **date_time_format** (
|
|
1299
|
+
* **date_time_format** (*str, optional, default: "dd MMM YYY HH:MM"*) – A POSIX
|
|
1228
1300
|
strftime-like formatting string of `Numbers date/time
|
|
1229
1301
|
directives <#datetime-formats>`_.
|
|
1230
1302
|
|
|
1231
1303
|
:``"fraction"``:
|
|
1232
|
-
* **fraction_accuracy** (
|
|
1304
|
+
* **fraction_accuracy** (*:py:class:`~numbers_parser.FractionAccuracy`, optional, default: FractionAccuracy.THREE* – The
|
|
1233
1305
|
precision of the faction.
|
|
1234
1306
|
|
|
1235
1307
|
:``"percentage"``:
|
|
1236
|
-
* **decimal_places** (
|
|
1308
|
+
* **decimal_places** (*float, optional, default: None*) – number of
|
|
1237
1309
|
decimal places, or ``None`` for automatic.
|
|
1238
|
-
* **negative_style** (
|
|
1310
|
+
* **negative_style** (*:py:class:`~numbers_parser.NegativeNumberStyle`, optional, default: NegativeNumberStyle.MINUS*) – How negative numbers are represented.
|
|
1239
1311
|
See `Negative number formats <#negative-formats>`_.
|
|
1240
|
-
* **show_thousands_separator** (
|
|
1312
|
+
* **show_thousands_separator** (*bool, optional, default: False*) – ``True``
|
|
1241
1313
|
if the number should include a thousands seperator, e.g. ``,``
|
|
1242
1314
|
|
|
1243
1315
|
:``"scientific"``:
|
|
1244
|
-
* **decimal_places** (
|
|
1316
|
+
* **decimal_places** (*float, optional, default: None*) – number of
|
|
1245
1317
|
decimal places, or ``None`` for automatic.
|
|
1246
1318
|
|
|
1247
|
-
|
|
1319
|
+
:``"tickbox"``:
|
|
1320
|
+
* No additional parameters defined.
|
|
1321
|
+
|
|
1322
|
+
:``"rating"``:
|
|
1323
|
+
* No additional parameters defined.
|
|
1324
|
+
|
|
1325
|
+
:``"slider"``:
|
|
1326
|
+
* **control_format** (*ControlFormattingType, optional, default: ControlFormattingType.NUMBER*) - the format
|
|
1327
|
+
of the data in the slider. Valid options are ``"base"``, ``"currency"``,
|
|
1328
|
+
``"datetime"``, ``"fraction"``, ``"percentage"``, ``"number"``,
|
|
1329
|
+
or ``"scientific". Each format allows additional parameters identical to those
|
|
1330
|
+
available for the formats themselves. For example, a slider using fractions
|
|
1331
|
+
is configured with ``fraction_accuracy``.
|
|
1332
|
+
* **increment** (*float, optional, default: 1*) - the slider's minimum value
|
|
1333
|
+
* **maximum** (*float, optional, default: 100*) - the slider's maximum value
|
|
1334
|
+
* **minimum** (*float, optional, default: 1*) - increment value for the slider
|
|
1335
|
+
|
|
1336
|
+
:`"stepper"``:
|
|
1337
|
+
* **control_format** (*ControlFormattingType, optional, default: ControlFormattingType.NUMBER*) - the format
|
|
1338
|
+
of the data in the stepper. Valid options are ``"base"``, ``"currency"``,
|
|
1339
|
+
``"datetime"``, ``"fraction"``, ``"percentage"``, ``"number"``,
|
|
1340
|
+
or ``"scientific"``. Each format allows additional parameters identical to those
|
|
1341
|
+
available for the formats themselves. For example, a stepper using fractions
|
|
1342
|
+
is configured with ``fraction_accuracy``.
|
|
1343
|
+
* **increment** (*float, optional, default: 1*) - the stepper's minimum value
|
|
1344
|
+
* **maximum** (*float, optional, default: 100*) - the stepper's maximum value
|
|
1345
|
+
* **minimum** (*float, optional, default: 1*) - increment value for the stepper
|
|
1346
|
+
|
|
1347
|
+
:`"popup"``:
|
|
1348
|
+
* **popup_values** (*List[str|int|float], optional, default: None*) – values
|
|
1349
|
+
for the popup menu
|
|
1350
|
+
* **allow_none** (*bool, optional, default: True*) - If ``True``
|
|
1351
|
+
include a blank value in the list
|
|
1248
1352
|
|
|
1249
|
-
.. code:: python
|
|
1250
|
-
|
|
1251
|
-
>>> table.set_cell_formatting("C1", "date", date_time_format="EEEE, d MMMM yyyy")
|
|
1252
|
-
>>> table.set_cell_formatting(0, 4, "number", decimal_places=3, negative_style=NegativeNumberStyle.RED)
|
|
1253
1353
|
|
|
1254
1354
|
""" # noqa: E501
|
|
1255
1355
|
(row, col, *args) = self._validate_cell_coords(*args)
|
|
@@ -1280,33 +1380,57 @@ class Table(Cacheable): # noqa: F811
|
|
|
1280
1380
|
raise TypeError("format must be a CustomFormatting object or format name")
|
|
1281
1381
|
|
|
1282
1382
|
cell = self._data[row][col]
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
elif custom_format.type == CustomFormattingType.TEXT and not isinstance(cell, TextCell):
|
|
1290
|
-
type_name = type(cell).__name__
|
|
1291
|
-
raise TypeError(f"cannot use text formatting for cells of type {type_name}")
|
|
1383
|
+
type_name = type(cell).__name__
|
|
1384
|
+
format_type_name = custom_format.type.name.lower()
|
|
1385
|
+
if type_name not in CUSTOM_FORMATTING_ALLOWED_CELLS[format_type_name]:
|
|
1386
|
+
raise TypeError(
|
|
1387
|
+
f"cannot use {format_type_name} formatting for cells of type {type_name}"
|
|
1388
|
+
)
|
|
1292
1389
|
|
|
1293
1390
|
format_id = self._model.custom_format_id(self._table_id, custom_format)
|
|
1294
1391
|
cell._set_formatting(format_id, custom_format.type)
|
|
1295
1392
|
|
|
1296
|
-
def _set_cell_data_format(self, row: int, col: int,
|
|
1393
|
+
def _set_cell_data_format(self, row: int, col: int, format_type_name: str, **kwargs) -> None:
|
|
1297
1394
|
try:
|
|
1298
|
-
format_type = FormattingType[
|
|
1395
|
+
format_type = FormattingType[format_type_name.upper()]
|
|
1299
1396
|
except (KeyError, AttributeError):
|
|
1300
|
-
raise TypeError(f"unsuported cell format type '{
|
|
1397
|
+
raise TypeError(f"unsuported cell format type '{format_type_name}'") from None
|
|
1301
1398
|
|
|
1302
1399
|
cell = self._data[row][col]
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
raise TypeError(
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
raise TypeError(f"cannot set formatting for cells of type {type_name}")
|
|
1400
|
+
type_name = type(cell).__name__
|
|
1401
|
+
if type_name not in FORMATTING_ALLOWED_CELLS[format_type_name]:
|
|
1402
|
+
raise TypeError(
|
|
1403
|
+
f"cannot use {format_type_name} formatting for cells of type {type_name}"
|
|
1404
|
+
)
|
|
1309
1405
|
|
|
1310
1406
|
format = Formatting(type=format_type, **kwargs)
|
|
1311
|
-
|
|
1312
|
-
|
|
1407
|
+
if format_type_name in FORMATTING_ACTION_CELLS:
|
|
1408
|
+
control_id = self._model.control_cell_archive(self._table_id, format_type, format)
|
|
1409
|
+
else:
|
|
1410
|
+
control_id = None
|
|
1411
|
+
|
|
1412
|
+
is_currency = True if format_type == FormattingType.CURRENCY else False
|
|
1413
|
+
if format_type_name in ["slider", "stepper"]:
|
|
1414
|
+
if "control_format" in kwargs:
|
|
1415
|
+
try:
|
|
1416
|
+
control_format = kwargs["control_format"].name
|
|
1417
|
+
number_format_type = FormattingType[control_format]
|
|
1418
|
+
is_currency = (
|
|
1419
|
+
True
|
|
1420
|
+
if kwargs["control_format"] == ControlFormattingType.CURRENCY
|
|
1421
|
+
else False
|
|
1422
|
+
)
|
|
1423
|
+
except (KeyError, AttributeError):
|
|
1424
|
+
raise TypeError(
|
|
1425
|
+
"unsupported number format '{control_format}' for format_type_name"
|
|
1426
|
+
) from None
|
|
1427
|
+
else:
|
|
1428
|
+
number_format_type = FormattingType.NUMBER
|
|
1429
|
+
format_id = self._model.format_archive(self._table_id, number_format_type, format)
|
|
1430
|
+
elif format_type_name == "popup":
|
|
1431
|
+
popup_format_type = FormattingType.TEXT if isinstance(cell, TextCell) else True
|
|
1432
|
+
format_id = self._model.format_archive(self._table_id, popup_format_type, format)
|
|
1433
|
+
else:
|
|
1434
|
+
format_id = self._model.format_archive(self._table_id, format_type, format)
|
|
1435
|
+
|
|
1436
|
+
cell._set_formatting(format_id, format_type, control_id, is_currency=is_currency)
|
numbers_parser/file.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import os
|
|
3
|
+
import re
|
|
3
4
|
from io import BytesIO
|
|
4
5
|
from sys import version_info
|
|
5
6
|
from zipfile import BadZipFile, ZipFile
|
|
@@ -32,11 +33,15 @@ def read_numbers_file(path, file_handler, object_handler=None):
|
|
|
32
33
|
read_numbers_file(filepath, file_handler, object_handler)
|
|
33
34
|
else:
|
|
34
35
|
f = open(filepath, "rb")
|
|
35
|
-
if filename.endswith(".iwa"):
|
|
36
|
-
blob = f.read()
|
|
37
|
-
extract_iwa_archives(blob, filepath, file_handler, object_handler)
|
|
38
36
|
blob = f.read()
|
|
39
|
-
|
|
37
|
+
if filename.endswith(".iwa"):
|
|
38
|
+
package_filepath = re.sub(r".*\.numbers/*", "", filepath)
|
|
39
|
+
extract_iwa_archives(blob, package_filepath, file_handler, object_handler)
|
|
40
|
+
else:
|
|
41
|
+
package_filepath = os.path.join(
|
|
42
|
+
re.sub(r".*\.numbers/*", "", path), filename
|
|
43
|
+
)
|
|
44
|
+
file_handler(package_filepath, blob)
|
|
40
45
|
else:
|
|
41
46
|
try:
|
|
42
47
|
zipf = open_zipfile(path)
|
numbers_parser/model.py
CHANGED
|
@@ -2,6 +2,7 @@ import math
|
|
|
2
2
|
import re
|
|
3
3
|
from array import array
|
|
4
4
|
from collections import defaultdict
|
|
5
|
+
from hashlib import sha1
|
|
5
6
|
from struct import pack
|
|
6
7
|
from typing import Dict, List, Tuple, Union
|
|
7
8
|
from warnings import warn
|
|
@@ -55,6 +56,7 @@ from numbers_parser.constants import (
|
|
|
55
56
|
FORMAT_TYPE_MAP,
|
|
56
57
|
MAX_TILE_SIZE,
|
|
57
58
|
PACKAGE_ID,
|
|
59
|
+
CellInteractionType,
|
|
58
60
|
FormatType,
|
|
59
61
|
)
|
|
60
62
|
from numbers_parser.containers import ObjectStore
|
|
@@ -219,6 +221,7 @@ class _NumbersModel(Cacheable):
|
|
|
219
221
|
self._table_formats = DataLists(self, "format_table", "format")
|
|
220
222
|
self._table_styles = DataLists(self, "styleTable", "reference")
|
|
221
223
|
self._table_strings = DataLists(self, "stringTable", "string")
|
|
224
|
+
self._control_specs = DataLists(self, "control_cell_spec_table", "cell_spec")
|
|
222
225
|
self._table_data = {}
|
|
223
226
|
self._styles = None
|
|
224
227
|
self._custom_formats = None
|
|
@@ -362,6 +365,64 @@ class _NumbersModel(Cacheable):
|
|
|
362
365
|
format = TSKArchives.FormatStructArchive(**attrs)
|
|
363
366
|
return self._table_formats.lookup_key(table_id, format)
|
|
364
367
|
|
|
368
|
+
def cell_popup_model(self, parent_id: int, format: Formatting):
|
|
369
|
+
tsce_items = [{"cell_value_type": "NIL_TYPE"}]
|
|
370
|
+
for item in format.popup_values:
|
|
371
|
+
if isinstance(item, str):
|
|
372
|
+
tsce_items.append(
|
|
373
|
+
{
|
|
374
|
+
"cell_value_type": "STRING_TYPE",
|
|
375
|
+
"string_value": {
|
|
376
|
+
"value": item,
|
|
377
|
+
"format": {"format_type": FormatType.TEXT},
|
|
378
|
+
},
|
|
379
|
+
}
|
|
380
|
+
)
|
|
381
|
+
else:
|
|
382
|
+
tsce_items.append(
|
|
383
|
+
{
|
|
384
|
+
"cell_value_type": "NUMBER_TYPE",
|
|
385
|
+
"number_value": {
|
|
386
|
+
"value": item,
|
|
387
|
+
"format": {"format_type": FormatType.DECIMAL},
|
|
388
|
+
},
|
|
389
|
+
}
|
|
390
|
+
)
|
|
391
|
+
popup_menu_id, _ = self.objects.create_object_from_dict(
|
|
392
|
+
f"Index/Tables/DataList-{parent_id}",
|
|
393
|
+
{"tsce_item": tsce_items},
|
|
394
|
+
TSTArchives.PopUpMenuModel,
|
|
395
|
+
True,
|
|
396
|
+
)
|
|
397
|
+
return popup_menu_id
|
|
398
|
+
|
|
399
|
+
def control_cell_archive(self, table_id: int, format_type: FormattingType, format: Formatting):
|
|
400
|
+
"""Create control cell archive from a Formatting spec and return the table format ID"""
|
|
401
|
+
if format_type == FormattingType.TICKBOX:
|
|
402
|
+
cell_spec = TSTArchives.CellSpecArchive(interaction_type=CellInteractionType.TOGGLE)
|
|
403
|
+
elif format_type == FormattingType.RATING:
|
|
404
|
+
cell_spec = TSTArchives.CellSpecArchive(
|
|
405
|
+
interaction_type=CellInteractionType.RATING,
|
|
406
|
+
range_control_min=0.0,
|
|
407
|
+
range_control_max=5.0,
|
|
408
|
+
range_control_inc=1.0,
|
|
409
|
+
)
|
|
410
|
+
elif format_type == FormattingType.SLIDER:
|
|
411
|
+
cell_spec = TSTArchives.CellSpecArchive(
|
|
412
|
+
interaction_type=CellInteractionType.SLIDER,
|
|
413
|
+
range_control_min=format.minimum,
|
|
414
|
+
range_control_max=format.maximum,
|
|
415
|
+
range_control_inc=format.increment,
|
|
416
|
+
)
|
|
417
|
+
else: # POPUP
|
|
418
|
+
popup_id = self.cell_popup_model(self._control_specs.id(table_id), format)
|
|
419
|
+
cell_spec = TSTArchives.CellSpecArchive(
|
|
420
|
+
interaction_type=CellInteractionType.POPUP,
|
|
421
|
+
chooser_control_popup_model=TSPMessages.Reference(identifier=popup_id),
|
|
422
|
+
chooser_control_start_w_first=not (format.allow_none),
|
|
423
|
+
)
|
|
424
|
+
return self._control_specs.lookup_key(table_id, cell_spec)
|
|
425
|
+
|
|
365
426
|
def add_custom_decimal_format_archive(self, format: CustomFormatting) -> None:
|
|
366
427
|
"""Create a custom format from the format spec"""
|
|
367
428
|
integer_format = format.integer_format
|
|
@@ -1476,12 +1537,35 @@ class _NumbersModel(Cacheable):
|
|
|
1476
1537
|
+ str(cell.style.bg_color.g)
|
|
1477
1538
|
+ str(cell.style.bg_color.b)
|
|
1478
1539
|
)
|
|
1540
|
+
if cell._style.bg_image is not None:
|
|
1541
|
+
fingerprint += cell._style.bg_image.filename
|
|
1479
1542
|
if fingerprint not in cell_styles:
|
|
1480
1543
|
cell_styles[fingerprint] = self.add_cell_style(cell._style)
|
|
1481
1544
|
cell._style._cell_style_obj_id = cell_styles[fingerprint]
|
|
1482
1545
|
|
|
1483
1546
|
def add_cell_style(self, style: Style) -> int:
|
|
1484
|
-
if style.
|
|
1547
|
+
if style.bg_image is not None:
|
|
1548
|
+
datas = self.objects[PACKAGE_ID].datas
|
|
1549
|
+
image_id = self.next_image_identifier()
|
|
1550
|
+
datas.append(
|
|
1551
|
+
TSPArchiveMessages.DataInfo(
|
|
1552
|
+
identifier=image_id,
|
|
1553
|
+
digest=sha1(style.bg_image.data).digest(),
|
|
1554
|
+
preferred_file_name=style.bg_image.filename,
|
|
1555
|
+
file_name=style.bg_image.filename,
|
|
1556
|
+
materialized_length=len(style.bg_image.data),
|
|
1557
|
+
)
|
|
1558
|
+
)
|
|
1559
|
+
color_attrs = {
|
|
1560
|
+
"cell_fill": {
|
|
1561
|
+
"image": {
|
|
1562
|
+
"technique": "ScaleToFill",
|
|
1563
|
+
"imagedata": {"identifier": image_id},
|
|
1564
|
+
"interpretsUntaggedImageDataAsGeneric": False,
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
elif style.bg_color is not None:
|
|
1485
1569
|
color_attrs = {
|
|
1486
1570
|
"cell_fill": {
|
|
1487
1571
|
"color": {
|
|
@@ -1530,6 +1614,7 @@ class _NumbersModel(Cacheable):
|
|
|
1530
1614
|
style=TSPMessages.Reference(identifier=cell_style_id),
|
|
1531
1615
|
)
|
|
1532
1616
|
)
|
|
1617
|
+
|
|
1533
1618
|
return cell_style_id
|
|
1534
1619
|
|
|
1535
1620
|
def text_style_object_id(self, cell_storage) -> int:
|
|
@@ -1698,6 +1783,10 @@ class _NumbersModel(Cacheable):
|
|
|
1698
1783
|
flags |= 0x200
|
|
1699
1784
|
length += 4
|
|
1700
1785
|
storage += pack("<i", cell._storage.formula_id)
|
|
1786
|
+
if cell._storage.control_id is not None:
|
|
1787
|
+
flags |= 0x400
|
|
1788
|
+
length += 4
|
|
1789
|
+
storage += pack("<i", cell._storage.control_id)
|
|
1701
1790
|
if cell._storage.suggest_id is not None:
|
|
1702
1791
|
flags |= 0x1000
|
|
1703
1792
|
length += 4
|
|
@@ -2203,6 +2292,20 @@ class _NumbersModel(Cacheable):
|
|
|
2203
2292
|
stroke_layer.stroke_runs.append(self.create_stroke(origin, length, border_value))
|
|
2204
2293
|
layer_ids.append(TSPMessages.Reference(identifier=stroke_layer_id))
|
|
2205
2294
|
|
|
2295
|
+
def store_image(self, data: bytes, filename: str) -> None:
|
|
2296
|
+
"""Store image data in the file store."""
|
|
2297
|
+
stored_filename = f"Data/{filename}"
|
|
2298
|
+
if stored_filename in self.objects.file_store:
|
|
2299
|
+
raise IndexError(f"{filename}: image already exists in document")
|
|
2300
|
+
self.objects.file_store[stored_filename] = data
|
|
2301
|
+
|
|
2302
|
+
def next_image_identifier(self):
|
|
2303
|
+
"""Return the next available ID in the list of images in the document."""
|
|
2304
|
+
datas = self.objects[PACKAGE_ID].datas
|
|
2305
|
+
image_ids = [x.identifier for x in datas]
|
|
2306
|
+
# datas never appears to be an empty list (default themes include images)
|
|
2307
|
+
return max(image_ids) + 1
|
|
2308
|
+
|
|
2206
2309
|
|
|
2207
2310
|
def rgb(obj) -> RGB:
|
|
2208
2311
|
"""Convert a TSPArchives.Color into an RGB tuple."""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: numbers-parser
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.9.0
|
|
4
4
|
Summary: Read and write Apple Numbers spreadsheets
|
|
5
5
|
Home-page: https://github.com/masaccio/numbers-parser
|
|
6
6
|
License: MIT
|
|
@@ -187,8 +187,8 @@ Whilst support for writing numbers files has been stable since version
|
|
|
187
187
|
and instead save data to a new file.
|
|
188
188
|
|
|
189
189
|
Cell values are written using
|
|
190
|
-
[Table.write()](https://masaccio.github.io/numbers-parser
|
|
191
|
-
|
|
190
|
+
[Table.write()](https://masaccio.github.io/numbers-parser/api/table.html#numbers_parser.Table.write) and
|
|
191
|
+
`numbers-parser` will automatically create empty rows and columns
|
|
192
192
|
for any cell references that are out of range of the current table.
|
|
193
193
|
|
|
194
194
|
```python
|
|
@@ -201,12 +201,9 @@ table.write("B7", datetime(2020, 12, 25))
|
|
|
201
201
|
doc.save("new-sheet.numbers")
|
|
202
202
|
```
|
|
203
203
|
|
|
204
|
-
Additional tables and worksheets can be added to a `Document` before
|
|
205
|
-
|
|
206
|
-
[
|
|
207
|
-
and
|
|
208
|
-
[Sheet.add_table()](https://masaccio.github.io/numbers-parser/#numbers_parser.Sheet.add_table)
|
|
209
|
-
respectively:
|
|
204
|
+
Additional tables and worksheets can be added to a `Document` before saving using
|
|
205
|
+
[Document.add_sheet()](https://masaccio.github.io/numbers-parser/api/document.html#numbers_parser.Document.add_sheet) and
|
|
206
|
+
[Sheet.add_table()](https://masaccio.github.io/numbers-parser/api/sheet.html#numbers_parser.Sheet.add_table) respectively:
|
|
210
207
|
|
|
211
208
|
```python
|
|
212
209
|
doc = Document()
|
|
@@ -222,27 +219,28 @@ doc.save("sheet.numbers")
|
|
|
222
219
|
### Styles
|
|
223
220
|
|
|
224
221
|
`numbers_parser` currently only supports paragraph styles and cell
|
|
225
|
-
styles. The following
|
|
222
|
+
styles. The following styles are supported:
|
|
226
223
|
|
|
227
224
|
- font attributes: bold, italic, underline, strikethrough
|
|
228
225
|
- font selection and size
|
|
229
226
|
- text foreground color
|
|
230
227
|
- horizontal and vertical alignment
|
|
231
228
|
- cell background color
|
|
229
|
+
- cell background images
|
|
232
230
|
- cell indents (first line, left, right, and text inset)
|
|
233
231
|
|
|
234
232
|
Numbers conflates style attributes that can be stored in paragraph
|
|
235
233
|
styles (the style menu in the text panel) with the settings that are
|
|
236
234
|
available on the Style tab of the Text panel. Some attributes in Numbers
|
|
237
|
-
are not applied to new cells when a style is applied.
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
object. When a document is saved, the attributes
|
|
241
|
-
paragraph style are applied to each cell that includes it.
|
|
235
|
+
are not applied to new cells when a style is applied.
|
|
236
|
+
|
|
237
|
+
To keep the API simple, `numbers-parser` packs all styling into a single
|
|
238
|
+
[Style](https://masaccio.github.io/numbers-parser/api/style.html) object. When a document is saved, the attributes
|
|
239
|
+
not stored in a paragraph style are applied to each cell that includes it.
|
|
242
240
|
|
|
243
241
|
Styles are read from cells using the
|
|
244
|
-
[Cell.style](https://masaccio.github.io/numbers-parser/api/cells.html#numbers_parser.Cell.style)
|
|
245
|
-
|
|
242
|
+
[Cell.style](https://masaccio.github.io/numbers-parser/api/cells.html#numbers_parser.Cell.style) property and you can
|
|
243
|
+
add new styles with
|
|
246
244
|
[Document.add_style](https://masaccio.github.io/numbers-parser/api/document.html#numbers_parser.Document.add_style).
|
|
247
245
|
|
|
248
246
|
```python
|
|
@@ -272,7 +270,7 @@ internally by the package. Changing a data format for cell has no impact
|
|
|
272
270
|
on any other cells.
|
|
273
271
|
|
|
274
272
|
Cell formats are changed using
|
|
275
|
-
[Table.set_cell_formatting](https://masaccio.github.io/numbers-parser
|
|
273
|
+
[Table.set_cell_formatting()](https://masaccio.github.io/numbers-parser/api/table.html#numbers_parser.Table.set_cell_formatting):
|
|
276
274
|
|
|
277
275
|
```python
|
|
278
276
|
table.set_cell_formatting(
|
|
@@ -293,9 +291,9 @@ Custom formats are shared across a Document and can be applied to
|
|
|
293
291
|
multiple cells in multiple tables. Editing a custom format changes the
|
|
294
292
|
appearance of data in all cells that share that format. You must first
|
|
295
293
|
add a custom format to the document using
|
|
296
|
-
[Document.add_custom_format](https://masaccio.github.io/numbers-parser
|
|
294
|
+
[Document.add_custom_format()](https://masaccio.github.io/numbers-parser/api/document.html#numbers_parser.Document.add_custom_format)
|
|
297
295
|
before assigning it to cells using
|
|
298
|
-
[Table.set_cell_formatting](https://masaccio.github.io/numbers-parser
|
|
296
|
+
[Table.set_cell_formatting()](https://masaccio.github.io/numbers-parser/api/table.html#numbers_parser.Table.set_cell_formatting):
|
|
299
297
|
|
|
300
298
|
```python
|
|
301
299
|
long_date = doc.add_custom_format(
|
|
@@ -322,13 +320,11 @@ table to allow for drawing borders across multiple cells. Setting the
|
|
|
322
320
|
border of merged cells is not possible unless the edge of the cells is
|
|
323
321
|
at the end of the merged region.
|
|
324
322
|
|
|
325
|
-
Borders are represented using the
|
|
326
|
-
|
|
327
|
-
class that can be initialized with line width, color and line style. The
|
|
323
|
+
Borders are represented using the [Border](https://masaccio.github.io/numbers-parser/api/border.html) class
|
|
324
|
+
that can be initialized with line width, color and line style. The
|
|
328
325
|
current state of a cell border is read using the
|
|
329
|
-
[Cell.border](https://masaccio.github.io/numbers-parser
|
|
330
|
-
|
|
331
|
-
[Table.set_cell_border](https://masaccio.github.io/numbers-parser/#numbers_parser.Table.set_cell_border)
|
|
326
|
+
[Cell.border](https://masaccio.github.io/numbers-parser/api/cells.html#numbers_parser.Cell.border) property
|
|
327
|
+
and [Table.set_cell_border()](https://masaccio.github.io/numbers-parser/api/table.html#numbers_parser.Table.set_cell_border)
|
|
332
328
|
sets the border for a cell edge or a range of cells.
|
|
333
329
|
|
|
334
330
|
## API
|
|
@@ -400,6 +396,5 @@ Current known limitations of `numbers-parser` are:
|
|
|
400
396
|
|
|
401
397
|
## License
|
|
402
398
|
|
|
403
|
-
All code in this repository is licensed under the [MIT
|
|
404
|
-
License](https://github.com/masaccio/numbers-parser/blob/master/LICENSE.rst)
|
|
399
|
+
All code in this repository is licensed under the [MIT License](https://github.com/masaccio/numbers-parser/blob/master/LICENSE.rst).
|
|
405
400
|
|
|
@@ -2,16 +2,16 @@ numbers_parser/__init__.py,sha256=hyoQ4x-1E8yDBMR128kZhSDLCfrnjNdz4_dEpKlekk4,19
|
|
|
2
2
|
numbers_parser/_cat_numbers.py,sha256=-HboBJT11Vjcr8sjLZl7Z6qAapnPEc_kFYq6PTqON20,4619
|
|
3
3
|
numbers_parser/_unpack_numbers.py,sha256=zfpBOfM92rMHSRQVR4tExf0fWI2Lbbb6WrwQPoq4gMo,5689
|
|
4
4
|
numbers_parser/bullets.py,sha256=OnVVMPjhTDrC-ncw52Gb00UEXNmn2Rvd3xi7lfqW3hk,2616
|
|
5
|
-
numbers_parser/cell.py,sha256=
|
|
6
|
-
numbers_parser/cell_storage.py,sha256=
|
|
7
|
-
numbers_parser/constants.py,sha256=
|
|
8
|
-
numbers_parser/containers.py,sha256=
|
|
5
|
+
numbers_parser/cell.py,sha256=YfHs1xxVSNY_oEodTN3IhqdO5tUhdgzZz5Z0m10dtpw,38457
|
|
6
|
+
numbers_parser/cell_storage.py,sha256=jaGpleFg8xWPT3U-O4bczAMYFR54t3x920UBKcrUiOQ,34354
|
|
7
|
+
numbers_parser/constants.py,sha256=BJGNz0ZZCs5xfAXlzLox4tvKOHIpDMXgJQ6i5yvdr4A,9606
|
|
8
|
+
numbers_parser/containers.py,sha256=yR_T2yF5QiVj7Dg22nCMLvo___Xrec3j8kitbxiaWyU,4220
|
|
9
9
|
numbers_parser/currencies.py,sha256=8k4a3WKmDoHeurkDICymHX13N7ManHSTaka_JNXCZYA,3767
|
|
10
10
|
numbers_parser/data/empty.numbers,sha256=8JOp035V-p2ff9_Wao7mLcYvb6_if6O2cus_esjVA9k,90316
|
|
11
|
-
numbers_parser/document.py,sha256=
|
|
11
|
+
numbers_parser/document.py,sha256=JmGyafm10FXnS5zmXkMTRVvYM-KnCKtcmbNCwMDyuSU,55333
|
|
12
12
|
numbers_parser/exceptions.py,sha256=G8dASUQZI8ksHYRVfdGWJzgsJD5CBpcZvmDJUZTqT-c,670
|
|
13
13
|
numbers_parser/experimental.py,sha256=WARjTa-2ePb8Ga8Q6oDP6EJCs12ofLRF2YpwzUu66ZI,374
|
|
14
|
-
numbers_parser/file.py,sha256=
|
|
14
|
+
numbers_parser/file.py,sha256=buNbZRzQCIlr7H4JxwTh2_eh7oDA2fGH5ZiFpIEVHoo,4200
|
|
15
15
|
numbers_parser/formula.py,sha256=JRsG0L21wS70oJ-FB46Amyoy-sKizWb-iUhSXUcVJ-U,10572
|
|
16
16
|
numbers_parser/generated/TNArchives_pb2.py,sha256=txkTtPHvdXVvv7zO1dHCxxnixaFulK7hJVLQrH3cIJc,16007
|
|
17
17
|
numbers_parser/generated/TNArchives_sos_pb2.py,sha256=AYI1X5t5Gb4l941jXlHEY0v97ToIze0bSYBR7KQmS0A,1215
|
|
@@ -50,11 +50,11 @@ numbers_parser/generated/fontmap.py,sha256=pqc1HwwTr3UbFMmhUaHJg1dX5-3pXbyhfS2bk
|
|
|
50
50
|
numbers_parser/generated/functionmap.py,sha256=VdZo0ERMYONcrnJFwABcSCHb8pjA4wY2ogt8Janz57M,6082
|
|
51
51
|
numbers_parser/iwafile.py,sha256=MuFIlB_hdXTTZflxoX_ZvA_68OaJkmRQ4eJ2UAiCKXQ,11833
|
|
52
52
|
numbers_parser/mapping.py,sha256=in8W3S8DmTcPefFaxnATLw0FQ4YnFsnAE-cl5dljzJE,32215
|
|
53
|
-
numbers_parser/model.py,sha256=
|
|
53
|
+
numbers_parser/model.py,sha256=qvU0RRF4YgUcfhtRlDdRP09gl8rUY0GNgeHpOTJYvHk,103312
|
|
54
54
|
numbers_parser/numbers_cache.py,sha256=1ghEBghQAYFpPiEeOtb74i016mXc039v1pOubbqvaLs,1141
|
|
55
55
|
numbers_parser/numbers_uuid.py,sha256=-LeAj_ULC0va57pEmyegGY0xXqkNNjyuLukCaiQJhOk,2642
|
|
56
|
-
numbers_parser-4.
|
|
57
|
-
numbers_parser-4.
|
|
58
|
-
numbers_parser-4.
|
|
59
|
-
numbers_parser-4.
|
|
60
|
-
numbers_parser-4.
|
|
56
|
+
numbers_parser-4.9.0.dist-info/LICENSE.rst,sha256=8vTa1-5KSdHrTpU9rlheO5005EWReEPMpjV7BjSaMc4,1050
|
|
57
|
+
numbers_parser-4.9.0.dist-info/METADATA,sha256=RWBbYXp1YHBV7Jj4Q6C_RwXIA_-JEi55l6o1A7G9r4w,16152
|
|
58
|
+
numbers_parser-4.9.0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
|
59
|
+
numbers_parser-4.9.0.dist-info/entry_points.txt,sha256=V91uB9vBPxf3eCY1h-0syv21imYCT0MJfMxf87DmwIk,115
|
|
60
|
+
numbers_parser-4.9.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|