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/document.py
CHANGED
|
@@ -1,11 +1,26 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import Dict, Iterator, List, Optional, Tuple, Union
|
|
2
2
|
from warnings import warn
|
|
3
3
|
|
|
4
|
-
from
|
|
4
|
+
from pendulum import DateTime, Duration
|
|
5
|
+
|
|
6
|
+
from numbers_parser.cell import (
|
|
7
|
+
Border,
|
|
8
|
+
Cell,
|
|
9
|
+
CustomFormatting,
|
|
10
|
+
CustomFormattingType,
|
|
11
|
+
DateCell,
|
|
12
|
+
Formatting,
|
|
13
|
+
FormattingType,
|
|
14
|
+
MergedCell,
|
|
15
|
+
NumberCell,
|
|
16
|
+
Style,
|
|
17
|
+
TextCell,
|
|
18
|
+
UnsupportedWarning,
|
|
19
|
+
xl_cell_to_rowcol,
|
|
20
|
+
)
|
|
5
21
|
from numbers_parser.cell_storage import CellStorage
|
|
6
22
|
from numbers_parser.constants import (
|
|
7
23
|
DEFAULT_COLUMN_COUNT,
|
|
8
|
-
DEFAULT_NUM_HEADERS,
|
|
9
24
|
DEFAULT_ROW_COUNT,
|
|
10
25
|
MAX_COL_COUNT,
|
|
11
26
|
MAX_HEADER_COUNT,
|
|
@@ -16,51 +31,68 @@ from numbers_parser.file import write_numbers_file
|
|
|
16
31
|
from numbers_parser.model import _NumbersModel
|
|
17
32
|
from numbers_parser.numbers_cache import Cacheable, cache
|
|
18
33
|
|
|
34
|
+
__all__ = ["Document", "Sheet", "Table"]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Sheet:
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class Table:
|
|
42
|
+
pass
|
|
43
|
+
|
|
19
44
|
|
|
20
45
|
class Document:
|
|
46
|
+
"""
|
|
47
|
+
Create an instance of a new Numbers document.
|
|
48
|
+
|
|
49
|
+
If ``filename`` is ``None``, an empty document is created using the defaults
|
|
50
|
+
defined by the class constructor. You can optionionally override these
|
|
51
|
+
defaults at object construction time.
|
|
52
|
+
|
|
53
|
+
Parameters
|
|
54
|
+
----------
|
|
55
|
+
filename: str, optional
|
|
56
|
+
Apple Numbers document to read.
|
|
57
|
+
sheet_name: *str*, *optional*, *default*: ``Sheet 1``
|
|
58
|
+
Name of the first sheet in a new document
|
|
59
|
+
table_name: *str*, *optional*, *default*: ``Table 1``
|
|
60
|
+
Name of the first table in the first sheet of a new
|
|
61
|
+
num_header_rows: int, optional, default: 1
|
|
62
|
+
Number of header rows in the first table of a new document.
|
|
63
|
+
num_header_cols: int, optional, default: 1
|
|
64
|
+
Number of header columns in the first table of a new document.
|
|
65
|
+
num_rows: int, optional, default: 12
|
|
66
|
+
Number of rows in the first table of a new document.
|
|
67
|
+
num_cols: int, optional, default: 8
|
|
68
|
+
Number of columns in the first table of a new document.
|
|
69
|
+
|
|
70
|
+
Raises
|
|
71
|
+
------
|
|
72
|
+
IndexError:
|
|
73
|
+
If the sheet name already exists in the document.
|
|
74
|
+
IndexError:
|
|
75
|
+
If the table name already exists in the first sheet.
|
|
76
|
+
"""
|
|
77
|
+
|
|
21
78
|
def __init__( # noqa: PLR0913
|
|
22
79
|
self,
|
|
23
|
-
filename: str = None,
|
|
24
|
-
sheet_name: str =
|
|
25
|
-
table_name: str =
|
|
26
|
-
num_header_rows: int =
|
|
27
|
-
num_header_cols: int =
|
|
28
|
-
num_rows: int =
|
|
29
|
-
num_cols: int =
|
|
80
|
+
filename: Optional[str] = None,
|
|
81
|
+
sheet_name: Optional[str] = "Sheet 1",
|
|
82
|
+
table_name: Optional[str] = "Table 1",
|
|
83
|
+
num_header_rows: Optional[int] = 1,
|
|
84
|
+
num_header_cols: Optional[int] = 1,
|
|
85
|
+
num_rows: Optional[int] = DEFAULT_ROW_COUNT,
|
|
86
|
+
num_cols: Optional[int] = DEFAULT_COLUMN_COUNT,
|
|
30
87
|
):
|
|
31
|
-
if filename is not None and (
|
|
32
|
-
(sheet_name is not None)
|
|
33
|
-
or (table_name is not None)
|
|
34
|
-
or (num_header_rows is not None)
|
|
35
|
-
or (num_header_cols is not None)
|
|
36
|
-
or (num_rows is not None)
|
|
37
|
-
or (num_cols is not None)
|
|
38
|
-
):
|
|
39
|
-
warn(
|
|
40
|
-
"can't set table/sheet attributes on load of existing document",
|
|
41
|
-
RuntimeWarning,
|
|
42
|
-
stacklevel=2,
|
|
43
|
-
)
|
|
44
|
-
|
|
45
88
|
self._model = _NumbersModel(filename)
|
|
46
89
|
refs = self._model.sheet_ids()
|
|
47
90
|
self._sheets = ItemsList(self._model, refs, Sheet)
|
|
48
91
|
|
|
49
92
|
if filename is None:
|
|
50
|
-
|
|
51
|
-
self.sheets[0].name = sheet_name
|
|
93
|
+
self.sheets[0].name = sheet_name
|
|
52
94
|
table = self.sheets[0].tables[0]
|
|
53
|
-
|
|
54
|
-
table.name = table_name
|
|
55
|
-
|
|
56
|
-
if num_header_rows is None:
|
|
57
|
-
num_header_rows = DEFAULT_NUM_HEADERS
|
|
58
|
-
if num_header_cols is None:
|
|
59
|
-
num_header_cols = DEFAULT_NUM_HEADERS
|
|
60
|
-
if num_rows is None:
|
|
61
|
-
num_rows = DEFAULT_ROW_COUNT
|
|
62
|
-
if num_cols is None:
|
|
63
|
-
num_cols = DEFAULT_COLUMN_COUNT
|
|
95
|
+
table.name = table_name
|
|
64
96
|
|
|
65
97
|
# Table starts as 1x1 with no headers
|
|
66
98
|
table.add_row(num_rows - 1)
|
|
@@ -69,30 +101,72 @@ class Document:
|
|
|
69
101
|
table.num_header_cols = num_header_cols
|
|
70
102
|
|
|
71
103
|
@property
|
|
72
|
-
def sheets(self) ->
|
|
73
|
-
"""
|
|
104
|
+
def sheets(self) -> List[Sheet]:
|
|
105
|
+
"""List[:class:`Sheet`]: A list of sheets in the document."""
|
|
74
106
|
return self._sheets
|
|
75
107
|
|
|
76
108
|
@property
|
|
77
|
-
def styles(self) ->
|
|
78
|
-
"""
|
|
109
|
+
def styles(self) -> Dict[str, Style]:
|
|
110
|
+
"""Dict[str, :class:`Style`]: A dict mapping style names to to the corresponding style."""
|
|
79
111
|
return self._model.styles
|
|
80
112
|
|
|
81
|
-
|
|
113
|
+
@property
|
|
114
|
+
def custom_formats(self) -> Dict[str, CustomFormatting]:
|
|
115
|
+
"""
|
|
116
|
+
Dict[str, :class:`CustomFormatting`]: A dict mapping custom format names
|
|
117
|
+
to the corresponding custom format.
|
|
118
|
+
"""
|
|
119
|
+
return self._model.custom_formats
|
|
120
|
+
|
|
121
|
+
def save(self, filename: str) -> None:
|
|
122
|
+
"""
|
|
123
|
+
Save the document in the specified filename.
|
|
124
|
+
|
|
125
|
+
Parameters
|
|
126
|
+
----------
|
|
127
|
+
filename: str
|
|
128
|
+
The path to save the document to. If the file already exists,
|
|
129
|
+
it will be overwritten.
|
|
130
|
+
"""
|
|
82
131
|
for sheet in self.sheets:
|
|
83
132
|
for table in sheet.tables:
|
|
84
|
-
self._model.
|
|
133
|
+
if self._model.is_a_pivot_table(table._table_id):
|
|
134
|
+
table_name = self._model.table_name(table._table_id)
|
|
135
|
+
warn(
|
|
136
|
+
f"Not modifying pivot table '{table_name}'",
|
|
137
|
+
UnsupportedWarning,
|
|
138
|
+
stacklevel=2,
|
|
139
|
+
)
|
|
140
|
+
else:
|
|
141
|
+
self._model.recalculate_table_data(table._table_id, table._data)
|
|
85
142
|
write_numbers_file(filename, self._model.file_store)
|
|
86
143
|
|
|
87
144
|
def add_sheet(
|
|
88
145
|
self,
|
|
89
|
-
sheet_name=None,
|
|
90
|
-
table_name=
|
|
91
|
-
num_rows=DEFAULT_ROW_COUNT,
|
|
92
|
-
num_cols=DEFAULT_COLUMN_COUNT,
|
|
93
|
-
) ->
|
|
94
|
-
"""
|
|
95
|
-
|
|
146
|
+
sheet_name: Optional[str] = None,
|
|
147
|
+
table_name: Optional[str] = "Table 1",
|
|
148
|
+
num_rows: Optional[int] = DEFAULT_ROW_COUNT,
|
|
149
|
+
num_cols: Optional[int] = DEFAULT_COLUMN_COUNT,
|
|
150
|
+
) -> None:
|
|
151
|
+
"""
|
|
152
|
+
Add a new sheet to the current document.
|
|
153
|
+
|
|
154
|
+
If no sheet name is provided, the next available numbered sheet
|
|
155
|
+
will be generated in the series ``Sheet 1``, ``Sheet 2``, etc.
|
|
156
|
+
|
|
157
|
+
Parameters
|
|
158
|
+
----------
|
|
159
|
+
sheet_name: str, optional
|
|
160
|
+
The name of the sheet to add to the document
|
|
161
|
+
table_name: *str*, *optional*, *default*: ``Table 1``
|
|
162
|
+
The name of the table created in the new sheet
|
|
163
|
+
num_rows: int, optional, default: 12
|
|
164
|
+
The number of columns in the newly created table
|
|
165
|
+
num_cols: int, optional, default: 8
|
|
166
|
+
The number of columns in the newly created table
|
|
167
|
+
|
|
168
|
+
Raises:
|
|
169
|
+
IndexError: If the sheet name already exists in the document.
|
|
96
170
|
"""
|
|
97
171
|
if sheet_name is not None:
|
|
98
172
|
if sheet_name in self._sheets:
|
|
@@ -103,9 +177,6 @@ class Document:
|
|
|
103
177
|
sheet_num += 1
|
|
104
178
|
sheet_name = f"Sheet {sheet_num}"
|
|
105
179
|
|
|
106
|
-
if table_name is None:
|
|
107
|
-
table_name = "Table 1"
|
|
108
|
-
|
|
109
180
|
prev_table_id = self._sheets[-1]._tables[0]._table_id
|
|
110
181
|
new_sheet_id = self._model.add_sheet(sheet_name)
|
|
111
182
|
new_sheet = Sheet(self._model, new_sheet_id)
|
|
@@ -119,12 +190,41 @@ class Document:
|
|
|
119
190
|
)
|
|
120
191
|
self._sheets.append(new_sheet)
|
|
121
192
|
|
|
122
|
-
return new_sheet
|
|
123
|
-
|
|
124
193
|
def add_style(self, **kwargs) -> Style:
|
|
125
|
-
"""
|
|
126
|
-
|
|
127
|
-
|
|
194
|
+
r"""
|
|
195
|
+
Add a new style to the current document.
|
|
196
|
+
|
|
197
|
+
If no style name is provided, the next available numbered style
|
|
198
|
+
will be generated in the series ``Custom Style 1``, ``Custom Style 2``, etc.
|
|
199
|
+
|
|
200
|
+
Parameters
|
|
201
|
+
----------
|
|
202
|
+
kwargs: dict, optional
|
|
203
|
+
Key-value pairs to pass to the :class:`Style` constructor
|
|
204
|
+
|
|
205
|
+
Raises
|
|
206
|
+
------
|
|
207
|
+
TypeError:
|
|
208
|
+
If ``font_size`` is not a ``float``, ``font_name`` is not a ``str``,
|
|
209
|
+
or if any of the ``bool`` parameters are invalid.
|
|
210
|
+
|
|
211
|
+
Example
|
|
212
|
+
-------
|
|
213
|
+
|
|
214
|
+
.. code-block:: python
|
|
215
|
+
|
|
216
|
+
red_text = doc.add_style(
|
|
217
|
+
name="Red Text",
|
|
218
|
+
font_name="Lucida Grande",
|
|
219
|
+
font_color=RGB(230, 25, 25),
|
|
220
|
+
font_size=14.0,
|
|
221
|
+
bold=True,
|
|
222
|
+
italic=True,
|
|
223
|
+
alignment=Alignment("right", "top"),
|
|
224
|
+
)
|
|
225
|
+
table.write("B2", "Red", style=red_text)
|
|
226
|
+
table.set_cell_style("C2", red_text)
|
|
227
|
+
""" # noqa: E501
|
|
128
228
|
if "name" in kwargs and kwargs["name"] is not None and kwargs["name"] in self._model.styles:
|
|
129
229
|
raise IndexError(f"style '{kwargs['name']}' already exists")
|
|
130
230
|
style = Style(**kwargs)
|
|
@@ -134,8 +234,89 @@ class Document:
|
|
|
134
234
|
self._model.styles[style.name] = style
|
|
135
235
|
return style
|
|
136
236
|
|
|
237
|
+
def add_custom_format(self, **kwargs) -> CustomFormatting:
|
|
238
|
+
r"""
|
|
239
|
+
Add a new custom format to the current document.
|
|
240
|
+
|
|
241
|
+
All custom formatting styles share a name and a type, described in the **Common**
|
|
242
|
+
parameters in the following table. Additional key-value pairs configure the format
|
|
243
|
+
depending upon the value of ``kwargs["type"]``. Supported values for
|
|
244
|
+
``kwargs["type"]`` are:
|
|
245
|
+
|
|
246
|
+
* ``"datetime"``: A date and time value with custom formatting.
|
|
247
|
+
* ``"number"``: A decimal number.
|
|
248
|
+
* ``"text"``: A simple text string.
|
|
249
|
+
|
|
250
|
+
:Common Keys:
|
|
251
|
+
* **name** (``str``) – The name of the custom format. If no name is provided,
|
|
252
|
+
one is generated using the scheme ``Custom Format``, ``Custom Format 1``, ``Custom Format 2``, etc.
|
|
253
|
+
* **type** (``str``, *optional*, default: ``number``) – The type of format to
|
|
254
|
+
create Supported formats are ``number``, ``datetime`` and ``text``.
|
|
255
|
+
|
|
256
|
+
:``"number"``:
|
|
257
|
+
* **integer_format** (``PaddingType``, *optional*, default: ``PaddingType.NONE``) – How
|
|
258
|
+
to pad integers.
|
|
259
|
+
* **decimal_format** (``PaddingType``, *optional*, default: ``PaddingType.NONE``) – How
|
|
260
|
+
to pad decimals.
|
|
261
|
+
* **num_integers** (``int``, *optional*, default: ``0``) – Integer precision
|
|
262
|
+
when integers are padded.
|
|
263
|
+
* **num_decimals** (``int``, *optional*, default: ``0``) – Integer precision
|
|
264
|
+
when decimals are padded.
|
|
265
|
+
* **show_thousands_separator** (``bool``, *optional*, default: ``False``) – ``True``
|
|
266
|
+
if the number should include a thousands seperator.
|
|
267
|
+
|
|
268
|
+
:``"datetime"``:
|
|
269
|
+
* **format** (``str``, *optional*, default: ``"d MMM y"``) – A POSIX strftime-like
|
|
270
|
+
formatting string of `Numbers date/time directives <#datetime-formats>`_.
|
|
271
|
+
|
|
272
|
+
:``"text"``:
|
|
273
|
+
* **format** (``str``, *optional*, default: ``"%s"``) – Text format.
|
|
274
|
+
The cell value is inserted in place of %s. Only one substitution is allowed by
|
|
275
|
+
Numbers, and multiple %s formatting references raise a TypeError exception
|
|
276
|
+
|
|
277
|
+
Example
|
|
278
|
+
-------
|
|
279
|
+
|
|
280
|
+
.. code-block:: python
|
|
281
|
+
|
|
282
|
+
long_date = doc.add_custom_format(
|
|
283
|
+
name="Long Date",
|
|
284
|
+
type="date",
|
|
285
|
+
date_time_format="EEEE, d MMMM yyyy")
|
|
286
|
+
table.set_cell_formatting("C1", "custom", format=long_date)
|
|
287
|
+
""" # noqa: E501
|
|
288
|
+
if (
|
|
289
|
+
"name" in kwargs
|
|
290
|
+
and kwargs["name"] is not None
|
|
291
|
+
and kwargs["name"] in self._model.custom_formats
|
|
292
|
+
):
|
|
293
|
+
raise IndexError(f"format '{kwargs['name']}' already exists")
|
|
294
|
+
|
|
295
|
+
if "type" in kwargs:
|
|
296
|
+
format_type = kwargs["type"].upper()
|
|
297
|
+
try:
|
|
298
|
+
kwargs["type"] = CustomFormattingType[format_type]
|
|
299
|
+
except (KeyError, AttributeError):
|
|
300
|
+
raise TypeError(f"unsuported cell format type '{format_type}'") from None
|
|
301
|
+
|
|
302
|
+
custom_format = CustomFormatting(**kwargs)
|
|
303
|
+
if custom_format.name is None:
|
|
304
|
+
custom_format.name = self._model.custom_format_name()
|
|
305
|
+
if custom_format.type == CustomFormattingType.NUMBER:
|
|
306
|
+
self._model.add_custom_decimal_format_archive(custom_format)
|
|
307
|
+
elif custom_format.type == CustomFormattingType.DATETIME:
|
|
308
|
+
self._model.add_custom_datetime_format_archive(custom_format)
|
|
309
|
+
else:
|
|
310
|
+
self._model.add_custom_text_format_archive(custom_format)
|
|
311
|
+
return custom_format
|
|
312
|
+
|
|
137
313
|
|
|
138
314
|
class Sheet:
|
|
315
|
+
"""
|
|
316
|
+
.. NOTE::
|
|
317
|
+
Do not instantiate directly. Sheets are created by :py:class:`~numbers_parser.Document`.
|
|
318
|
+
"""
|
|
319
|
+
|
|
139
320
|
def __init__(self, model, sheet_id):
|
|
140
321
|
self._sheet_id = sheet_id
|
|
141
322
|
self._model = model
|
|
@@ -143,29 +324,66 @@ class Sheet:
|
|
|
143
324
|
self._tables = ItemsList(self._model, refs, Table)
|
|
144
325
|
|
|
145
326
|
@property
|
|
146
|
-
def tables(self):
|
|
327
|
+
def tables(self) -> List[Table]:
|
|
328
|
+
"""List[:class:`Table`]: A list of tables in the sheet."""
|
|
147
329
|
return self._tables
|
|
148
330
|
|
|
149
331
|
@property
|
|
150
|
-
def name(self):
|
|
151
|
-
"""
|
|
332
|
+
def name(self) -> str:
|
|
333
|
+
"""str: The name of the sheet."""
|
|
152
334
|
return self._model.sheet_name(self._sheet_id)
|
|
153
335
|
|
|
154
336
|
@name.setter
|
|
155
|
-
def name(self, value):
|
|
156
|
-
"""Set the sheet's name."""
|
|
337
|
+
def name(self, value: str):
|
|
157
338
|
self._model.sheet_name(self._sheet_id, value)
|
|
158
339
|
|
|
159
340
|
def add_table( # noqa: PLR0913
|
|
160
341
|
self,
|
|
161
|
-
table_name=None,
|
|
162
|
-
x=None,
|
|
163
|
-
y=None,
|
|
164
|
-
num_rows=DEFAULT_ROW_COUNT,
|
|
165
|
-
num_cols=DEFAULT_COLUMN_COUNT,
|
|
166
|
-
) ->
|
|
167
|
-
"""Add a new table to the current sheet.
|
|
168
|
-
|
|
342
|
+
table_name: Optional[str] = None,
|
|
343
|
+
x: Optional[float] = None,
|
|
344
|
+
y: Optional[float] = None,
|
|
345
|
+
num_rows: Optional[int] = DEFAULT_ROW_COUNT,
|
|
346
|
+
num_cols: Optional[int] = DEFAULT_COLUMN_COUNT,
|
|
347
|
+
) -> Table:
|
|
348
|
+
"""Add a new table to the current sheet.
|
|
349
|
+
|
|
350
|
+
If no table name is provided, the next available numbered table
|
|
351
|
+
will be generated in the series ``Table 1``, ``Table 2``, etc.
|
|
352
|
+
|
|
353
|
+
By default, new tables are positioned at a fixed offset below the last
|
|
354
|
+
table vertically in a sheet and on the left side of the sheet. Large
|
|
355
|
+
table headers and captions may result in new tables overlapping existing
|
|
356
|
+
ones. The ``add_table`` method takes optional coordinates for
|
|
357
|
+
positioning a table. A table's height and coordinates can also be
|
|
358
|
+
queried to help aligning new tables:
|
|
359
|
+
|
|
360
|
+
.. code:: python
|
|
361
|
+
|
|
362
|
+
(x, y) = sheet.table[0].coordinates
|
|
363
|
+
y += sheet.table[0].height + 200.0
|
|
364
|
+
new_table = sheet.add_table("Offset Table", x, y)
|
|
365
|
+
|
|
366
|
+
Parameters
|
|
367
|
+
----------
|
|
368
|
+
table_name: str, optional
|
|
369
|
+
The name of the new table.
|
|
370
|
+
x: float, optional
|
|
371
|
+
The x offset for the table in points.
|
|
372
|
+
y: float, optional
|
|
373
|
+
The y offset for the table in points.
|
|
374
|
+
num_rows: int, optional, default: 12
|
|
375
|
+
The number of rows for the new table.
|
|
376
|
+
num_cols: int, optional, default: 10
|
|
377
|
+
The number of columns for the new table.
|
|
378
|
+
|
|
379
|
+
Returns
|
|
380
|
+
-------
|
|
381
|
+
Table
|
|
382
|
+
The newly created table.
|
|
383
|
+
|
|
384
|
+
Raises
|
|
385
|
+
------
|
|
386
|
+
IndexError: If the table name already exists.
|
|
169
387
|
"""
|
|
170
388
|
from_table_id = self._tables[-1]._table_id
|
|
171
389
|
return self._add_table(table_name, from_table_id, x, y, num_rows, num_cols)
|
|
@@ -189,7 +407,12 @@ class Sheet:
|
|
|
189
407
|
return self._tables[-1]
|
|
190
408
|
|
|
191
409
|
|
|
192
|
-
class Table(Cacheable):
|
|
410
|
+
class Table(Cacheable): # noqa: F811
|
|
411
|
+
"""
|
|
412
|
+
.. NOTE::
|
|
413
|
+
Do not instantiate directly. Tables are created by :py:class:`~numbers_parser.Document`.
|
|
414
|
+
"""
|
|
415
|
+
|
|
193
416
|
def __init__(self, model, table_id):
|
|
194
417
|
super().__init__()
|
|
195
418
|
self._model = model
|
|
@@ -202,45 +425,52 @@ class Table(Cacheable):
|
|
|
202
425
|
self._model.set_table_data(table_id, self._data)
|
|
203
426
|
merge_cells = self._model.merge_cells(table_id)
|
|
204
427
|
|
|
205
|
-
for
|
|
428
|
+
for row in range(self.num_rows):
|
|
206
429
|
self._data.append([])
|
|
207
|
-
for
|
|
208
|
-
cell_storage = model.table_cell_decode(table_id,
|
|
430
|
+
for col in range(self.num_cols):
|
|
431
|
+
cell_storage = model.table_cell_decode(table_id, row, col)
|
|
209
432
|
if cell_storage is None:
|
|
210
|
-
if merge_cells.is_merge_reference((
|
|
211
|
-
cell = Cell.merged_cell(table_id,
|
|
433
|
+
if merge_cells.is_merge_reference((row, col)):
|
|
434
|
+
cell = Cell.merged_cell(table_id, row, col, model)
|
|
212
435
|
else:
|
|
213
|
-
cell = Cell.empty_cell(table_id,
|
|
436
|
+
cell = Cell.empty_cell(table_id, row, col, model)
|
|
214
437
|
else:
|
|
215
438
|
cell = Cell.from_storage(cell_storage)
|
|
216
|
-
self._data[
|
|
439
|
+
self._data[row].append(cell)
|
|
217
440
|
|
|
218
441
|
@property
|
|
219
442
|
def name(self) -> str:
|
|
220
|
-
"""
|
|
443
|
+
"""str: The table's name."""
|
|
221
444
|
return self._model.table_name(self._table_id)
|
|
222
445
|
|
|
223
446
|
@name.setter
|
|
224
447
|
def name(self, value: str):
|
|
225
|
-
"""Set the table's name."""
|
|
226
448
|
self._model.table_name(self._table_id, value)
|
|
227
449
|
|
|
228
450
|
@property
|
|
229
|
-
def table_name_enabled(self):
|
|
451
|
+
def table_name_enabled(self) -> bool:
|
|
452
|
+
"""bool: ``True`` if the table name is visible, ``False`` otherwise."""
|
|
230
453
|
return self._model.table_name_enabled(self._table_id)
|
|
231
454
|
|
|
232
455
|
@table_name_enabled.setter
|
|
233
|
-
def table_name_enabled(self, enabled):
|
|
456
|
+
def table_name_enabled(self, enabled: bool):
|
|
234
457
|
self._model.table_name_enabled(self._table_id, enabled)
|
|
235
458
|
|
|
236
459
|
@property
|
|
237
460
|
def num_header_rows(self) -> int:
|
|
238
|
-
"""
|
|
461
|
+
"""
|
|
462
|
+
int: The number of header rows.
|
|
463
|
+
|
|
464
|
+
Raises
|
|
465
|
+
------
|
|
466
|
+
ValueError:
|
|
467
|
+
If the number of headers is negative, exceeds the number of rows in the
|
|
468
|
+
table, or exceeds Numbers maxinum number of headers (``MAX_HEADER_COUNT``).
|
|
469
|
+
"""
|
|
239
470
|
return self._model.num_header_rows(self._table_id)
|
|
240
471
|
|
|
241
472
|
@num_header_rows.setter
|
|
242
473
|
def num_header_rows(self, num_headers: int):
|
|
243
|
-
"""Return the number of header rows."""
|
|
244
474
|
if num_headers < 0:
|
|
245
475
|
raise ValueError("Number of headers cannot be negative")
|
|
246
476
|
elif num_headers > self.num_rows:
|
|
@@ -251,12 +481,19 @@ class Table(Cacheable):
|
|
|
251
481
|
|
|
252
482
|
@property
|
|
253
483
|
def num_header_cols(self) -> int:
|
|
254
|
-
"""
|
|
484
|
+
"""
|
|
485
|
+
int: The number of header columns.
|
|
486
|
+
|
|
487
|
+
Raises
|
|
488
|
+
------
|
|
489
|
+
ValueError:
|
|
490
|
+
If the number of headers is negative, exceeds the number of rows in the
|
|
491
|
+
table, or exceeds Numbers maxinum number of headers (``MAX_HEADER_COUNT``).
|
|
492
|
+
"""
|
|
255
493
|
return self._model.num_header_cols(self._table_id)
|
|
256
494
|
|
|
257
495
|
@num_header_cols.setter
|
|
258
496
|
def num_header_cols(self, num_headers: int):
|
|
259
|
-
"""Return the number of header columns."""
|
|
260
497
|
if num_headers < 0:
|
|
261
498
|
raise ValueError("Number of headers cannot be negative")
|
|
262
499
|
elif num_headers > self.num_cols:
|
|
@@ -267,35 +504,66 @@ class Table(Cacheable):
|
|
|
267
504
|
|
|
268
505
|
@property
|
|
269
506
|
def height(self) -> int:
|
|
270
|
-
"""
|
|
507
|
+
"""int: The table's height in points."""
|
|
271
508
|
return self._model.table_height(self._table_id)
|
|
272
509
|
|
|
273
510
|
@property
|
|
274
511
|
def width(self) -> int:
|
|
275
|
-
"""
|
|
512
|
+
"""int: The table's width in points."""
|
|
276
513
|
return self._model.table_width(self._table_id)
|
|
277
514
|
|
|
278
|
-
def row_height(self,
|
|
279
|
-
"""
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
515
|
+
def row_height(self, row: int, height: int = None) -> int:
|
|
516
|
+
"""
|
|
517
|
+
The height of a table row in points.
|
|
518
|
+
|
|
519
|
+
Parameters
|
|
520
|
+
----------
|
|
521
|
+
row: int
|
|
522
|
+
The row number (zero indexed).
|
|
523
|
+
height: int
|
|
524
|
+
The height of the row in points. If not ``None``, set the row height.
|
|
525
|
+
|
|
526
|
+
Returns
|
|
527
|
+
-------
|
|
528
|
+
int:
|
|
529
|
+
The height of the table row.
|
|
530
|
+
"""
|
|
531
|
+
return self._model.row_height(self._table_id, row, height)
|
|
532
|
+
|
|
533
|
+
def col_width(self, col: int, width: int = None) -> int:
|
|
534
|
+
"""The width of a table column in points.
|
|
535
|
+
|
|
536
|
+
Parameters
|
|
537
|
+
----------
|
|
538
|
+
col: int
|
|
539
|
+
The column number (zero indexed).
|
|
540
|
+
width: int
|
|
541
|
+
The width of the column in points. If not ``None``, set the column width.
|
|
542
|
+
|
|
543
|
+
Returns
|
|
544
|
+
-------
|
|
545
|
+
int:
|
|
546
|
+
The width of the table column.
|
|
547
|
+
"""
|
|
548
|
+
return self._model.col_width(self._table_id, col, width)
|
|
285
549
|
|
|
286
550
|
@property
|
|
287
551
|
def coordinates(self) -> Tuple[float]:
|
|
288
|
-
"""
|
|
552
|
+
"""Tuple[float]: The table's x, y offsets in points."""
|
|
289
553
|
return self._model.table_coordinates(self._table_id)
|
|
290
554
|
|
|
291
|
-
def rows(self, values_only: bool = False) ->
|
|
555
|
+
def rows(self, values_only: bool = False) -> Union[List[List[Cell]], List[List[str]]]:
|
|
292
556
|
"""Return all rows of cells for the Table.
|
|
293
557
|
|
|
294
|
-
|
|
295
|
-
|
|
558
|
+
Parameters
|
|
559
|
+
----------
|
|
560
|
+
values_only:
|
|
561
|
+
If ``True``, return cell values instead of :class:`Cell` objects
|
|
296
562
|
|
|
297
|
-
Returns
|
|
298
|
-
|
|
563
|
+
Returns
|
|
564
|
+
-------
|
|
565
|
+
List[List[Cell]] | List[List[str]]:
|
|
566
|
+
List of rows; each row is a list of :class:`Cell` objects, or string values.
|
|
299
567
|
"""
|
|
300
568
|
if values_only:
|
|
301
569
|
return [[cell.value for cell in row] for row in self._data]
|
|
@@ -304,48 +572,122 @@ class Table(Cacheable):
|
|
|
304
572
|
|
|
305
573
|
@property
|
|
306
574
|
@cache(num_args=0)
|
|
307
|
-
def merge_ranges(self) ->
|
|
575
|
+
def merge_ranges(self) -> List[str]:
|
|
576
|
+
"""List[str]: The merge ranges of cells in A1 notation.
|
|
577
|
+
|
|
578
|
+
Example
|
|
579
|
+
-------
|
|
580
|
+
|
|
581
|
+
.. code-block:: python
|
|
582
|
+
|
|
583
|
+
>>> table.merge_ranges
|
|
584
|
+
['A4:A10']
|
|
585
|
+
>>> table.cell("A4")
|
|
586
|
+
<numbers_parser.cell.TextCell object at 0x1035f4a90>
|
|
587
|
+
>>> table.cell("A5")
|
|
588
|
+
<numbers_parser.cell.MergedCell object at 0x1035f5310>
|
|
589
|
+
"""
|
|
308
590
|
merge_cells = self._model.merge_cells(self._table_id).merge_cell_names()
|
|
309
591
|
return sorted(set(list(merge_cells)))
|
|
310
592
|
|
|
311
593
|
def cell(self, *args) -> Union[Cell, MergedCell]:
|
|
594
|
+
"""
|
|
595
|
+
Return a single cell in the table.
|
|
596
|
+
|
|
597
|
+
The ``cell()`` method supports two forms of notation to designate the position
|
|
598
|
+
of cells: **Row-column** notation and **A1** notation:
|
|
599
|
+
|
|
600
|
+
.. code-block:: python
|
|
601
|
+
|
|
602
|
+
(0, 0) # Row-column notation.
|
|
603
|
+
("A1") # The same cell in A1 notation.
|
|
604
|
+
|
|
605
|
+
Parameters
|
|
606
|
+
----------
|
|
607
|
+
param1: int
|
|
608
|
+
The row number (zero indexed).
|
|
609
|
+
param2: int
|
|
610
|
+
The column number (zero indexed).
|
|
611
|
+
|
|
612
|
+
Returns
|
|
613
|
+
-------
|
|
614
|
+
Cell | MergedCell:
|
|
615
|
+
A cell with the base class :class:`Cell` or, if merged, a :class:`MergedCell`.
|
|
616
|
+
|
|
617
|
+
Example
|
|
618
|
+
-------
|
|
619
|
+
|
|
620
|
+
.. code-block:: python
|
|
621
|
+
|
|
622
|
+
>>> doc = Document("mydoc.numbers")
|
|
623
|
+
>>> sheets = doc.sheets
|
|
624
|
+
>>> tables = sheets["Sheet 1"].tables
|
|
625
|
+
>>> table = tables["Table 1"]
|
|
626
|
+
>>> table.cell(1,0)
|
|
627
|
+
<numbers_parser.cell.TextCell object at 0x105a80a10>
|
|
628
|
+
>>> table.cell(1,0).value
|
|
629
|
+
'Debit'
|
|
630
|
+
>>> table.cell("B2")
|
|
631
|
+
<numbers_parser.cell.TextCell object at 0x105a80b90>
|
|
632
|
+
>>> table.cell("B2").value
|
|
633
|
+
1234.50
|
|
634
|
+
""" # noqa: E501
|
|
312
635
|
if isinstance(args[0], str):
|
|
313
|
-
(
|
|
636
|
+
(row, col) = xl_cell_to_rowcol(args[0])
|
|
314
637
|
elif len(args) != 2:
|
|
315
638
|
raise IndexError("invalid cell reference " + str(args))
|
|
316
639
|
else:
|
|
317
|
-
(
|
|
640
|
+
(row, col) = args
|
|
318
641
|
|
|
319
|
-
if
|
|
320
|
-
raise IndexError(f"row {
|
|
321
|
-
if
|
|
322
|
-
raise IndexError(f"column {
|
|
642
|
+
if row >= self.num_rows or row < 0:
|
|
643
|
+
raise IndexError(f"row {row} out of range")
|
|
644
|
+
if col >= self.num_cols or col < 0:
|
|
645
|
+
raise IndexError(f"column {col} out of range")
|
|
323
646
|
|
|
324
|
-
return self._data[
|
|
647
|
+
return self._data[row][col]
|
|
325
648
|
|
|
326
649
|
def iter_rows( # noqa: PLR0913
|
|
327
650
|
self,
|
|
328
|
-
min_row: int = None,
|
|
329
|
-
max_row: int = None,
|
|
330
|
-
min_col: int = None,
|
|
331
|
-
max_col: int = None,
|
|
332
|
-
values_only: bool = False,
|
|
333
|
-
) ->
|
|
334
|
-
"""Produces cells from a table, by row.
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
651
|
+
min_row: Optional[int] = None,
|
|
652
|
+
max_row: Optional[int] = None,
|
|
653
|
+
min_col: Optional[int] = None,
|
|
654
|
+
max_col: Optional[int] = None,
|
|
655
|
+
values_only: Optional[bool] = False,
|
|
656
|
+
) -> Iterator[Union[Tuple[Cell], Tuple[str]]]:
|
|
657
|
+
"""Produces cells from a table, by row.
|
|
658
|
+
|
|
659
|
+
Specify the iteration range using the indexes of the rows and columns.
|
|
660
|
+
|
|
661
|
+
Parameters
|
|
662
|
+
----------
|
|
663
|
+
min_row: int, optional
|
|
664
|
+
Starting row number (zero indexed), or ``0`` if ``None``.
|
|
665
|
+
max_row: int, optional
|
|
666
|
+
End row number (zero indexed), or all rows if ``None``.
|
|
667
|
+
min_col: int, optional
|
|
668
|
+
Starting column number (zero indexed) or ``0`` if ``None``.
|
|
669
|
+
max_col: int, optional
|
|
670
|
+
End column number (zero indexed), or all columns if ``None``.
|
|
671
|
+
values_only: bool, optional
|
|
672
|
+
If ``True``, yield cell values rather than :class:`Cell` objects
|
|
673
|
+
|
|
674
|
+
Yields
|
|
675
|
+
------
|
|
676
|
+
Tuple[Cell] | Tuple[str]:
|
|
677
|
+
:class:`Cell` objects or string values for the row
|
|
678
|
+
|
|
679
|
+
Raises
|
|
680
|
+
------
|
|
681
|
+
IndexError:
|
|
682
|
+
If row or column values are out of range for the table
|
|
683
|
+
|
|
684
|
+
Example
|
|
685
|
+
-------
|
|
686
|
+
|
|
687
|
+
.. code:: python
|
|
688
|
+
|
|
689
|
+
for row in table.iter_rows(min_row=2, max_row=7, values_only=True):
|
|
690
|
+
sum += row
|
|
349
691
|
"""
|
|
350
692
|
min_row = min_row or 0
|
|
351
693
|
max_row = max_row or self.num_rows - 1
|
|
@@ -362,35 +704,54 @@ class Table(Cacheable):
|
|
|
362
704
|
raise IndexError(f"column {max_col} out of range")
|
|
363
705
|
|
|
364
706
|
rows = self.rows()
|
|
365
|
-
for
|
|
707
|
+
for row in range(min_row, max_row + 1):
|
|
366
708
|
if values_only:
|
|
367
|
-
yield tuple(cell.value for cell in rows[
|
|
709
|
+
yield tuple(cell.value for cell in rows[row][min_col : max_col + 1])
|
|
368
710
|
else:
|
|
369
|
-
yield tuple(rows[
|
|
711
|
+
yield tuple(rows[row][min_col : max_col + 1])
|
|
370
712
|
|
|
371
713
|
def iter_cols( # noqa: PLR0913
|
|
372
714
|
self,
|
|
373
|
-
min_col: int = None,
|
|
374
|
-
max_col: int = None,
|
|
375
|
-
min_row: int = None,
|
|
376
|
-
max_row: int = None,
|
|
377
|
-
values_only: bool = False,
|
|
378
|
-
) ->
|
|
379
|
-
"""Produces cells from a table, by column.
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
715
|
+
min_col: Optional[int] = None,
|
|
716
|
+
max_col: Optional[int] = None,
|
|
717
|
+
min_row: Optional[int] = None,
|
|
718
|
+
max_row: Optional[int] = None,
|
|
719
|
+
values_only: Optional[bool] = False,
|
|
720
|
+
) -> Iterator[Union[Tuple[Cell], Tuple[str]]]:
|
|
721
|
+
"""Produces cells from a table, by column.
|
|
722
|
+
|
|
723
|
+
Specify the iteration range using the indexes of the rows and columns.
|
|
724
|
+
|
|
725
|
+
Parameters
|
|
726
|
+
----------
|
|
727
|
+
min_col: int, optional
|
|
728
|
+
Starting column number (zero indexed) or ``0`` if ``None``.
|
|
729
|
+
max_col: int, optional
|
|
730
|
+
End column number (zero indexed), or all columns if ``None``.
|
|
731
|
+
min_row: int, optional
|
|
732
|
+
Starting row number (zero indexed), or ``0`` if ``None``.
|
|
733
|
+
max_row: int, optional
|
|
734
|
+
End row number (zero indexed), or all rows if ``None``.
|
|
735
|
+
values_only: bool, optional
|
|
736
|
+
If ``True``, yield cell values rather than :class:`Cell` objects.
|
|
737
|
+
|
|
738
|
+
Yields
|
|
739
|
+
------
|
|
740
|
+
Tuple[Cell] | Tuple[str]:
|
|
741
|
+
:class:`Cell` objects or string values for the row
|
|
742
|
+
|
|
743
|
+
Raises
|
|
744
|
+
------
|
|
745
|
+
IndexError:
|
|
746
|
+
If row or column values are out of range for the table
|
|
747
|
+
|
|
748
|
+
Example
|
|
749
|
+
=======
|
|
750
|
+
|
|
751
|
+
.. code:: python
|
|
752
|
+
|
|
753
|
+
for col in table.iter_cols(min_row=2, max_row=7):
|
|
754
|
+
sum += col.value
|
|
394
755
|
"""
|
|
395
756
|
min_row = min_row or 0
|
|
396
757
|
max_row = max_row or self.num_rows - 1
|
|
@@ -407,91 +768,287 @@ class Table(Cacheable):
|
|
|
407
768
|
raise IndexError(f"column {max_col} out of range")
|
|
408
769
|
|
|
409
770
|
rows = self.rows()
|
|
410
|
-
for
|
|
771
|
+
for col in range(min_col, max_col + 1):
|
|
411
772
|
if values_only:
|
|
412
|
-
yield tuple(row[
|
|
773
|
+
yield tuple(row[col].value for row in rows[min_row : max_row + 1])
|
|
413
774
|
else:
|
|
414
|
-
yield tuple(row[
|
|
775
|
+
yield tuple(row[col] for row in rows[min_row : max_row + 1])
|
|
415
776
|
|
|
416
777
|
def _validate_cell_coords(self, *args):
|
|
417
|
-
"""Check first arguments are value cell references and pad
|
|
418
|
-
the table with empty cells if outside current bounds.
|
|
419
|
-
"""
|
|
420
778
|
if isinstance(args[0], str):
|
|
421
|
-
(
|
|
779
|
+
(row, col) = xl_cell_to_rowcol(args[0])
|
|
422
780
|
values = args[1:]
|
|
423
781
|
elif len(args) < 2:
|
|
424
782
|
raise IndexError("invalid cell reference " + str(args))
|
|
425
783
|
else:
|
|
426
|
-
(
|
|
784
|
+
(row, col) = args[0:2]
|
|
427
785
|
values = args[2:]
|
|
428
786
|
|
|
429
|
-
if
|
|
430
|
-
raise IndexError(f"{
|
|
431
|
-
if
|
|
432
|
-
raise IndexError(f"{
|
|
787
|
+
if row >= MAX_ROW_COUNT:
|
|
788
|
+
raise IndexError(f"{row} exceeds maximum row {MAX_ROW_COUNT-1}")
|
|
789
|
+
if col >= MAX_COL_COUNT:
|
|
790
|
+
raise IndexError(f"{col} exceeds maximum column {MAX_COL_COUNT-1}")
|
|
433
791
|
|
|
434
|
-
for _ in range(self.num_rows,
|
|
792
|
+
for _ in range(self.num_rows, row + 1):
|
|
435
793
|
self.add_row()
|
|
436
794
|
|
|
437
|
-
for _ in range(self.num_cols,
|
|
795
|
+
for _ in range(self.num_cols, col + 1):
|
|
438
796
|
self.add_column()
|
|
439
797
|
|
|
440
|
-
return (
|
|
798
|
+
return (row, col) + tuple(values)
|
|
441
799
|
|
|
442
|
-
def write(self, *args, style
|
|
800
|
+
def write(self, *args, style: Optional[Union[Style, str, None]] = None) -> None:
|
|
801
|
+
"""
|
|
802
|
+
Write a value to a cell and update the style/cell type.
|
|
803
|
+
|
|
804
|
+
The ``write()`` method supports two forms of notation to designate the position
|
|
805
|
+
of cells: **Row-column** notation and **A1** notation:
|
|
806
|
+
|
|
807
|
+
.. code-block:: python
|
|
808
|
+
|
|
809
|
+
(0, 0) # Row-column notation.
|
|
810
|
+
("A1") # The same cell in A1 notation.
|
|
811
|
+
|
|
812
|
+
Parameters
|
|
813
|
+
----------
|
|
814
|
+
|
|
815
|
+
param1: int
|
|
816
|
+
The row number (zero indexed)
|
|
817
|
+
param2: int
|
|
818
|
+
The column number (zero indexed)
|
|
819
|
+
param3: str | int | float | bool | DateTime | Duration
|
|
820
|
+
The value to write to the cell. The generated cell type
|
|
821
|
+
|
|
822
|
+
Warns
|
|
823
|
+
-----
|
|
824
|
+
RuntimeWarning:
|
|
825
|
+
If the default value is a float that is rounded to the maximum number
|
|
826
|
+
of supported digits.
|
|
827
|
+
|
|
828
|
+
Raises
|
|
829
|
+
------
|
|
830
|
+
IndexError:
|
|
831
|
+
If the style name cannot be foiund in the document.
|
|
832
|
+
TypeError:
|
|
833
|
+
If the style parameter is an invalid type.
|
|
834
|
+
ValueError:
|
|
835
|
+
If the cell type cannot be determined from the type of `param3`.
|
|
836
|
+
|
|
837
|
+
Example
|
|
838
|
+
-------
|
|
839
|
+
|
|
840
|
+
.. code:: python
|
|
841
|
+
|
|
842
|
+
doc = Document("write.numbers")
|
|
843
|
+
sheets = doc.sheets
|
|
844
|
+
tables = sheets[0].tables
|
|
845
|
+
table = tables[0]
|
|
846
|
+
table.write(1, 1, "This is new text")
|
|
847
|
+
table.write("B7", datetime(2020, 12, 25))
|
|
848
|
+
doc.save("new-sheet.numbers")
|
|
849
|
+
"""
|
|
443
850
|
# TODO: write needs to retain/init the border
|
|
444
|
-
(
|
|
445
|
-
self._data[
|
|
446
|
-
self.
|
|
447
|
-
|
|
448
|
-
)
|
|
851
|
+
(row, col, value) = self._validate_cell_coords(*args)
|
|
852
|
+
self._data[row][col] = Cell.from_value(row, col, value)
|
|
853
|
+
storage = CellStorage(self._model, self._table_id, None, row, col)
|
|
854
|
+
storage.update_value(value, self._data[row][col])
|
|
855
|
+
self._data[row][col].update_storage(storage)
|
|
856
|
+
|
|
449
857
|
merge_cells = self._model.merge_cells(self._table_id)
|
|
450
|
-
self._data[
|
|
451
|
-
self._data[
|
|
452
|
-
self._data[
|
|
858
|
+
self._data[row][col]._table_id = self._table_id
|
|
859
|
+
self._data[row][col]._model = self._model
|
|
860
|
+
self._data[row][col]._set_merge(merge_cells.get((row, col)))
|
|
453
861
|
|
|
454
862
|
if style is not None:
|
|
455
|
-
self.set_cell_style(
|
|
456
|
-
if formatting is not None:
|
|
457
|
-
self.set_cell_formatting(row_num, col_num, formatting)
|
|
863
|
+
self.set_cell_style(row, col, style)
|
|
458
864
|
|
|
459
865
|
def set_cell_style(self, *args):
|
|
460
|
-
(
|
|
866
|
+
(row, col, style) = self._validate_cell_coords(*args)
|
|
461
867
|
if isinstance(style, Style):
|
|
462
|
-
self._data[
|
|
868
|
+
self._data[row][col]._style = style
|
|
463
869
|
elif isinstance(style, str):
|
|
464
870
|
if style not in self._model.styles:
|
|
465
871
|
raise IndexError(f"style '{style}' does not exist")
|
|
466
|
-
self._data[
|
|
872
|
+
self._data[row][col]._style = self._model.styles[style]
|
|
467
873
|
else:
|
|
468
874
|
raise TypeError("style must be a Style object or style name")
|
|
469
875
|
|
|
470
|
-
def
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
876
|
+
def add_row(
|
|
877
|
+
self,
|
|
878
|
+
num_rows: Optional[int] = 1,
|
|
879
|
+
start_row: Optional[Union[int, None]] = None,
|
|
880
|
+
default: Optional[Union[str, int, float, bool, DateTime, Duration]] = None,
|
|
881
|
+
) -> None:
|
|
882
|
+
"""
|
|
883
|
+
Add or insert rows to the table.
|
|
884
|
+
|
|
885
|
+
Parameters
|
|
886
|
+
----------
|
|
887
|
+
num_rows: int, optional, default: 1
|
|
888
|
+
The number of rows to add to the table.
|
|
889
|
+
start_row: int, optional, default: None
|
|
890
|
+
The start row number (zero indexed), or ``None`` to add a row to
|
|
891
|
+
the end of the table.
|
|
892
|
+
default: str | int | float | bool | DateTime | Duration, optional, default: None
|
|
893
|
+
The default value for cells. Supported values are those supported by
|
|
894
|
+
:py:meth:`numbers_parser.Table.write` which will determine the new
|
|
895
|
+
cell type.
|
|
896
|
+
|
|
897
|
+
Warns
|
|
898
|
+
-----
|
|
899
|
+
RuntimeWarning:
|
|
900
|
+
If the default value is a float that is rounded to the maximum number
|
|
901
|
+
of supported digits.
|
|
902
|
+
|
|
903
|
+
Raises
|
|
904
|
+
------
|
|
905
|
+
IndexError:
|
|
906
|
+
If the start_row is out of range for the table.
|
|
907
|
+
ValueError:
|
|
908
|
+
If the default value is unsupported by :py:meth:`numbers_parser.Table.write`.
|
|
909
|
+
"""
|
|
910
|
+
if start_row is not None and (start_row < 0 or start_row >= self.num_rows):
|
|
911
|
+
raise IndexError("Row number not in range for table")
|
|
474
912
|
|
|
475
|
-
|
|
913
|
+
if start_row is None:
|
|
914
|
+
start_row = self.num_rows
|
|
915
|
+
self.num_rows += num_rows
|
|
916
|
+
self._model.number_of_rows(self._table_id, self.num_rows)
|
|
476
917
|
|
|
477
|
-
def add_row(self, num_rows=1):
|
|
478
918
|
row = [
|
|
479
|
-
Cell.empty_cell(self._table_id, self.num_rows - 1,
|
|
480
|
-
for
|
|
919
|
+
Cell.empty_cell(self._table_id, self.num_rows - 1, col, self._model)
|
|
920
|
+
for col in range(self.num_cols)
|
|
481
921
|
]
|
|
482
|
-
for _ in range(num_rows)
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
922
|
+
rows = [row.copy() for _ in range(num_rows)]
|
|
923
|
+
self._data[start_row:start_row] = rows
|
|
924
|
+
|
|
925
|
+
if default is not None:
|
|
926
|
+
for row in range(start_row, start_row + num_rows):
|
|
927
|
+
for col in range(self.num_cols):
|
|
928
|
+
self.write(row, col, default)
|
|
929
|
+
|
|
930
|
+
def add_column(
|
|
931
|
+
self,
|
|
932
|
+
num_cols: Optional[int] = 1,
|
|
933
|
+
start_col: Optional[Union[int, None]] = None,
|
|
934
|
+
default: Optional[Union[str, int, float, bool, DateTime, Duration]] = None,
|
|
935
|
+
) -> None:
|
|
936
|
+
"""
|
|
937
|
+
Add or insert columns to the table.
|
|
938
|
+
|
|
939
|
+
Parameters
|
|
940
|
+
----------
|
|
941
|
+
num_cols: int, optional, default: 1
|
|
942
|
+
The number of columns to add to the table.
|
|
943
|
+
start_col: int, optional, default: None
|
|
944
|
+
The start column number (zero indexed), or ``None`` to add a column to
|
|
945
|
+
the end of the table.
|
|
946
|
+
default: str | int | float | bool | DateTime | Duration, optional, default: None
|
|
947
|
+
The default value for cells. Supported values are those supported by
|
|
948
|
+
:py:meth:`numbers_parser.Table.write` which will determine the new
|
|
949
|
+
cell type.
|
|
950
|
+
|
|
951
|
+
Warns
|
|
952
|
+
-----
|
|
953
|
+
RuntimeWarning:
|
|
954
|
+
If the default value is a float that is rounded to the maximum number
|
|
955
|
+
of supported digits.
|
|
956
|
+
|
|
957
|
+
Raises
|
|
958
|
+
------
|
|
959
|
+
IndexError:
|
|
960
|
+
If the start_col is out of range for the table.
|
|
961
|
+
ValueError:
|
|
962
|
+
If the default value is unsupported by :py:meth:`numbers_parser.Table.write`.
|
|
963
|
+
"""
|
|
964
|
+
if start_col is not None and (start_col < 0 or start_col >= self.num_cols):
|
|
965
|
+
raise IndexError("Column number not in range for table")
|
|
966
|
+
|
|
967
|
+
if start_col is None:
|
|
968
|
+
start_col = self.num_cols
|
|
969
|
+
self.num_cols += num_cols
|
|
970
|
+
self._model.number_of_columns(self._table_id, self.num_cols)
|
|
971
|
+
|
|
972
|
+
for row in range(self.num_rows):
|
|
973
|
+
cols = [
|
|
974
|
+
Cell.empty_cell(self._table_id, row, col, self._model) for col in range(num_cols)
|
|
975
|
+
]
|
|
976
|
+
self._data[row][start_col:start_col] = cols
|
|
977
|
+
|
|
978
|
+
if default is not None:
|
|
979
|
+
for col in range(start_col, start_col + num_cols):
|
|
980
|
+
self.write(row, col, default)
|
|
981
|
+
|
|
982
|
+
def delete_row(
|
|
983
|
+
self,
|
|
984
|
+
num_rows: Optional[int] = 1,
|
|
985
|
+
start_row: Optional[Union[int, None]] = None,
|
|
986
|
+
) -> None:
|
|
987
|
+
"""
|
|
988
|
+
Delete rows from the table.
|
|
989
|
+
|
|
990
|
+
Parameters
|
|
991
|
+
----------
|
|
992
|
+
num_rows: int, optional, default: 1
|
|
993
|
+
The number of rows to add to the table.
|
|
994
|
+
start_row: int, optional, default: None
|
|
995
|
+
The start row number (zero indexed), or ``None`` to delete rows
|
|
996
|
+
from the end of the table.
|
|
997
|
+
|
|
998
|
+
Warns
|
|
999
|
+
-----
|
|
1000
|
+
RuntimeWarning:
|
|
1001
|
+
If the default value is a float that is rounded to the maximum number
|
|
1002
|
+
of supported digits.
|
|
1003
|
+
|
|
1004
|
+
Raises
|
|
1005
|
+
------
|
|
1006
|
+
IndexError:
|
|
1007
|
+
If the start_row is out of range for the table.
|
|
1008
|
+
"""
|
|
1009
|
+
if start_row is not None and (start_row < 0 or start_row >= self.num_rows):
|
|
1010
|
+
raise IndexError("Row number not in range for table")
|
|
1011
|
+
|
|
1012
|
+
if start_row is not None:
|
|
1013
|
+
del self._data[start_row : start_row + num_rows]
|
|
1014
|
+
else:
|
|
1015
|
+
del self._data[-num_rows:]
|
|
1016
|
+
|
|
1017
|
+
self.num_rows -= num_rows
|
|
1018
|
+
self._model.number_of_rows(self._table_id, self.num_rows)
|
|
1019
|
+
|
|
1020
|
+
def delete_column(
|
|
1021
|
+
self,
|
|
1022
|
+
num_cols: Optional[int] = 1,
|
|
1023
|
+
start_col: Optional[Union[int, None]] = None,
|
|
1024
|
+
) -> None:
|
|
1025
|
+
"""
|
|
1026
|
+
Add or delete columns columns from the table.
|
|
1027
|
+
|
|
1028
|
+
Parameters
|
|
1029
|
+
----------
|
|
1030
|
+
num_cols: int, optional, default: 1
|
|
1031
|
+
The number of columns to add to the table.
|
|
1032
|
+
start_col: int, optional, default: None
|
|
1033
|
+
The start column number (zero indexed), or ``None`` to add delete columns
|
|
1034
|
+
from the end of the table.
|
|
1035
|
+
|
|
1036
|
+
Raises
|
|
1037
|
+
------
|
|
1038
|
+
IndexError:
|
|
1039
|
+
If the start_col is out of range for the table.
|
|
1040
|
+
"""
|
|
1041
|
+
if start_col is not None and (start_col < 0 or start_col >= self.num_cols):
|
|
1042
|
+
raise IndexError("Column number not in range for table")
|
|
1043
|
+
|
|
1044
|
+
for row in range(self.num_rows):
|
|
1045
|
+
if start_col is not None:
|
|
1046
|
+
del self._data[row][start_col : start_col + num_cols]
|
|
1047
|
+
else:
|
|
1048
|
+
del self._data[row][-num_cols:]
|
|
1049
|
+
|
|
1050
|
+
self.num_cols -= num_cols
|
|
1051
|
+
self._model.number_of_columns(self._table_id, self.num_cols)
|
|
495
1052
|
|
|
496
1053
|
def merge_cells(self, cell_range):
|
|
497
1054
|
"""Convert a cell range or list of cell ranges into merged cells."""
|
|
@@ -507,19 +1064,17 @@ class Table(Cacheable):
|
|
|
507
1064
|
|
|
508
1065
|
merge_cells = self._model.merge_cells(self._table_id)
|
|
509
1066
|
merge_cells.add_anchor(row_start, col_start, (num_rows, num_cols))
|
|
510
|
-
for
|
|
511
|
-
for
|
|
512
|
-
self._data[
|
|
513
|
-
merge_cells.add_reference(
|
|
514
|
-
row_num, col_num, (row_start, col_start, row_end, col_end)
|
|
515
|
-
)
|
|
1067
|
+
for row in range(row_start + 1, row_end + 1):
|
|
1068
|
+
for col in range(col_start + 1, col_end + 1):
|
|
1069
|
+
self._data[row][col] = MergedCell(row, col)
|
|
1070
|
+
merge_cells.add_reference(row, col, (row_start, col_start, row_end, col_end))
|
|
516
1071
|
|
|
517
|
-
for
|
|
518
|
-
for
|
|
519
|
-
cell._set_merge(merge_cells.get((
|
|
1072
|
+
for row, cells in enumerate(self._data):
|
|
1073
|
+
for col, cell in enumerate(cells):
|
|
1074
|
+
cell._set_merge(merge_cells.get((row, col)))
|
|
520
1075
|
|
|
521
1076
|
def set_cell_border(self, *args):
|
|
522
|
-
(
|
|
1077
|
+
(row, col, *args) = self._validate_cell_coords(*args)
|
|
523
1078
|
if len(args) == 2:
|
|
524
1079
|
(side, border_value) = args
|
|
525
1080
|
length = 1
|
|
@@ -536,12 +1091,12 @@ class Table(Cacheable):
|
|
|
536
1091
|
|
|
537
1092
|
if isinstance(side, list):
|
|
538
1093
|
for s in side:
|
|
539
|
-
self.set_cell_border(
|
|
1094
|
+
self.set_cell_border(row, col, s, border_value, length)
|
|
540
1095
|
return
|
|
541
1096
|
|
|
542
|
-
if self._data[
|
|
1097
|
+
if self._data[row][col].is_merged and side in ["bottom", "right"]:
|
|
543
1098
|
warn(
|
|
544
|
-
f"cell [{
|
|
1099
|
+
f"cell [{row},{col}] is merged; {side} border not set",
|
|
545
1100
|
RuntimeWarning,
|
|
546
1101
|
stacklevel=2,
|
|
547
1102
|
)
|
|
@@ -550,16 +1105,165 @@ class Table(Cacheable):
|
|
|
550
1105
|
self._model.extract_strokes(self._table_id)
|
|
551
1106
|
|
|
552
1107
|
if side in ["top", "bottom"]:
|
|
553
|
-
for border_col_num in range(
|
|
554
|
-
self._model.set_cell_border(
|
|
555
|
-
self._table_id, row_num, border_col_num, side, border_value
|
|
556
|
-
)
|
|
1108
|
+
for border_col_num in range(col, col + length):
|
|
1109
|
+
self._model.set_cell_border(self._table_id, row, border_col_num, side, border_value)
|
|
557
1110
|
elif side in ["left", "right"]:
|
|
558
|
-
for border_row_num in range(
|
|
559
|
-
self._model.set_cell_border(
|
|
560
|
-
self._table_id, border_row_num, col_num, side, border_value
|
|
561
|
-
)
|
|
1111
|
+
for border_row_num in range(row, row + length):
|
|
1112
|
+
self._model.set_cell_border(self._table_id, border_row_num, col, side, border_value)
|
|
562
1113
|
else:
|
|
563
1114
|
raise TypeError("side must be a valid border segment")
|
|
564
1115
|
|
|
565
|
-
self._model.add_stroke(self._table_id,
|
|
1116
|
+
self._model.add_stroke(self._table_id, row, col, side, border_value, length)
|
|
1117
|
+
|
|
1118
|
+
def set_cell_formatting(self, *args: str, **kwargs) -> None:
|
|
1119
|
+
r"""
|
|
1120
|
+
Set the data format for a cell.
|
|
1121
|
+
|
|
1122
|
+
Cell references can be **row-column** offsers or Excel/Numbers-style **A1** notation.
|
|
1123
|
+
|
|
1124
|
+
:Parameters:
|
|
1125
|
+
* **args** (*list*, *optional*) – Positional arguments for cell reference and data format type (see below)
|
|
1126
|
+
* **kwargs** (*dict*, *optional*) - Key-value pairs defining a formatting options for each data format (see below).
|
|
1127
|
+
|
|
1128
|
+
:Args (row-column):
|
|
1129
|
+
* **param1** (``int``): The row number (zero indexed).
|
|
1130
|
+
* **param2** (``int``): The column number (zero indexed).
|
|
1131
|
+
* **param3** (``str``): Data format type for the cell (see "data formats" below).
|
|
1132
|
+
|
|
1133
|
+
:Args (A1):
|
|
1134
|
+
* **param1** (``str``): A cell reference using Excel/Numbers-style A1 notation.
|
|
1135
|
+
* **param2** (``str``): Data format type for the cell (see "data formats" below).
|
|
1136
|
+
|
|
1137
|
+
:Warns:
|
|
1138
|
+
* **RuntimeWarning** -
|
|
1139
|
+
If ``use_accounting_style`` is used with
|
|
1140
|
+
any ``negative_style`` other than ``NegativeNumberStyle.MINUS``.
|
|
1141
|
+
|
|
1142
|
+
All formatting styles share a name and a type, described in the **Common**
|
|
1143
|
+
parameters in the following table. Additional key-value pairs configure the format
|
|
1144
|
+
depending upon the value of ``kwargs["type"]``. Supported values for
|
|
1145
|
+
``kwargs["type"]`` are:
|
|
1146
|
+
|
|
1147
|
+
* ``"base"``: A number base in the range 2-36.
|
|
1148
|
+
* ``"currency"``: A decimal formatted with a currency symbol.
|
|
1149
|
+
* ``"datetime"``: A date and time value with custom formatting.
|
|
1150
|
+
* ``"fraction"``: A number formatted as the nearest fraction.
|
|
1151
|
+
* ``"percentage"``: A number formatted as a percentage
|
|
1152
|
+
* ``"number"``: A decimal number.
|
|
1153
|
+
* ``"scientific"``: A decimal number with scientific notation.
|
|
1154
|
+
|
|
1155
|
+
:Common keys:
|
|
1156
|
+
* **name** (``str``) – The name of the custom format. If no name is provided,
|
|
1157
|
+
one is generated using the scheme ``Custom Format``, ``Custom Format 1``, ``Custom Format 2``, etc.
|
|
1158
|
+
* **type** (``str``, *optional*, default: ``number``) – The type of format to
|
|
1159
|
+
create Supported formats are ``number``, ``datetime`` and ``text``.
|
|
1160
|
+
|
|
1161
|
+
:``"base"``:
|
|
1162
|
+
* **base_use_minus_sign** (``int``, *optional*, default: ``10``) – The integer
|
|
1163
|
+
base to represent the number from 2-36.
|
|
1164
|
+
* **base_use_minus_sign** (``bool``, *optional*, default: ``True``) – If ``True``
|
|
1165
|
+
use a standard minus sign, otherwise format as two's compliment (only
|
|
1166
|
+
possible for binary, octal and hexadecimal.
|
|
1167
|
+
* **base_places** (``int``, *optional*, default: ``0``) – The number of
|
|
1168
|
+
decimal places, or ``None`` for automatic.
|
|
1169
|
+
|
|
1170
|
+
:``"currency"``:
|
|
1171
|
+
* **currency** (``str``, *optional*, default: ``"GBP"``) – An ISO currency
|
|
1172
|
+
code, e.g. ``"GBP"`` or ``"USD"``.
|
|
1173
|
+
* **decimal_places** (``int``, *optional*, default: ``2``) – The number of
|
|
1174
|
+
decimal places, or ``None`` for automatic.
|
|
1175
|
+
* **negative_style** (``NegativeNumberStyle``, *optional*, default: ``NegativeNumberStyle.MINUS``) – How negative numbers are represented.
|
|
1176
|
+
See `Negative number formats <#negative-formats>`_.
|
|
1177
|
+
* **show_thousands_separator** (``bool``, *optional*, default: ``False``) – ``True``
|
|
1178
|
+
if the number should include a thousands seperator, e.g. ``,``
|
|
1179
|
+
* **use_accounting_style** (``bool``, *optional*, default: ``False``) – ``True``
|
|
1180
|
+
if the currency symbol should be formatted to the left of the cell and
|
|
1181
|
+
separated from the number value by a tab.
|
|
1182
|
+
|
|
1183
|
+
:``"datetime"``:
|
|
1184
|
+
* **date_time_format** (``str``, *optional*, default: ``"dd MMM YYY HH:MM"``) – A POSIX
|
|
1185
|
+
strftime-like formatting string of `Numbers date/time
|
|
1186
|
+
directives <#datetime-formats>`_.
|
|
1187
|
+
|
|
1188
|
+
:``"fraction"``:
|
|
1189
|
+
* **fraction_accuracy** (``FractionAccuracy``, *optional*, default: ``FractionAccuracy.THREE`` – The
|
|
1190
|
+
precision of the faction.
|
|
1191
|
+
|
|
1192
|
+
:``"percentage"``:
|
|
1193
|
+
* **decimal_places** (``float``, *optional*, default: ``None``) – number of
|
|
1194
|
+
decimal places, or ``None`` for automatic.
|
|
1195
|
+
* **negative_style** (``NegativeNumberStyle``, *optional*, default: ``NegativeNumberStyle.MINUS``) – How negative numbers are represented.
|
|
1196
|
+
See `Negative number formats <#negative-formats>`_.
|
|
1197
|
+
* **show_thousands_separator** (``bool``, *optional*, default: ``False``) – ``True``
|
|
1198
|
+
if the number should include a thousands seperator, e.g. ``,``
|
|
1199
|
+
|
|
1200
|
+
:``"scientific"``:
|
|
1201
|
+
* **decimal_places** (``float``, *optional*, default: ``None``) – number of
|
|
1202
|
+
decimal places, or ``None`` for automatic.
|
|
1203
|
+
|
|
1204
|
+
Example
|
|
1205
|
+
|
|
1206
|
+
.. code:: python
|
|
1207
|
+
|
|
1208
|
+
>>> table.set_cell_formatting("C1", "date", date_time_format="EEEE, d MMMM yyyy")
|
|
1209
|
+
>>> table.set_cell_formatting(0, 4, "number", decimal_places=3, negative_style=NegativeNumberStyle.RED)
|
|
1210
|
+
|
|
1211
|
+
""" # noqa: E501
|
|
1212
|
+
(row, col, *args) = self._validate_cell_coords(*args)
|
|
1213
|
+
if len(args) == 1:
|
|
1214
|
+
format_type = args[0]
|
|
1215
|
+
elif len(args) > 1:
|
|
1216
|
+
raise TypeError("too many positional arguments to set_cell_formatting")
|
|
1217
|
+
else:
|
|
1218
|
+
raise TypeError("no type defined for cell format")
|
|
1219
|
+
|
|
1220
|
+
if format_type == "custom":
|
|
1221
|
+
self.set_cell_custom_format(row, col, **kwargs)
|
|
1222
|
+
else:
|
|
1223
|
+
self.set_cell_data_format(row, col, format_type, **kwargs)
|
|
1224
|
+
|
|
1225
|
+
def set_cell_custom_format(self, row: int, col: int, **kwargs) -> None:
|
|
1226
|
+
if "format" not in kwargs:
|
|
1227
|
+
raise TypeError("no format provided for custom format")
|
|
1228
|
+
|
|
1229
|
+
custom_format = kwargs["format"]
|
|
1230
|
+
if isinstance(custom_format, CustomFormatting):
|
|
1231
|
+
custom_format = kwargs["format"]
|
|
1232
|
+
elif isinstance(custom_format, str):
|
|
1233
|
+
if custom_format not in self._model.custom_formats:
|
|
1234
|
+
raise IndexError(f"format '{custom_format}' does not exist")
|
|
1235
|
+
custom_format = self._model.custom_formats[custom_format]
|
|
1236
|
+
else:
|
|
1237
|
+
raise TypeError("format must be a CustomFormatting object or format name")
|
|
1238
|
+
|
|
1239
|
+
cell = self._data[row][col]
|
|
1240
|
+
if custom_format.type == CustomFormattingType.DATETIME and not isinstance(cell, DateCell):
|
|
1241
|
+
type_name = type(cell).__name__
|
|
1242
|
+
raise TypeError(f"cannot use date/time formatting for cells of type {type_name}")
|
|
1243
|
+
elif custom_format.type == CustomFormattingType.NUMBER and not isinstance(cell, NumberCell):
|
|
1244
|
+
type_name = type(cell).__name__
|
|
1245
|
+
raise TypeError(f"cannot use number formatting for cells of type {type_name}")
|
|
1246
|
+
elif custom_format.type == CustomFormattingType.TEXT and not isinstance(cell, TextCell):
|
|
1247
|
+
type_name = type(cell).__name__
|
|
1248
|
+
raise TypeError(f"cannot use text formatting for cells of type {type_name}")
|
|
1249
|
+
|
|
1250
|
+
format_id = self._model.custom_format_id(self._table_id, custom_format)
|
|
1251
|
+
cell._set_formatting(format_id, custom_format.type)
|
|
1252
|
+
|
|
1253
|
+
def set_cell_data_format(self, row: int, col: int, format_type: str, **kwargs) -> None:
|
|
1254
|
+
try:
|
|
1255
|
+
format_type = FormattingType[format_type.upper()]
|
|
1256
|
+
except (KeyError, AttributeError):
|
|
1257
|
+
raise TypeError(f"unsuported cell format type '{format_type}'") from None
|
|
1258
|
+
|
|
1259
|
+
cell = self._data[row][col]
|
|
1260
|
+
if format_type == FormattingType.DATETIME and not isinstance(cell, DateCell):
|
|
1261
|
+
type_name = type(cell).__name__
|
|
1262
|
+
raise TypeError(f"cannot use date/time formatting for cells of type {type_name}")
|
|
1263
|
+
elif not isinstance(cell, NumberCell) and not isinstance(cell, DateCell):
|
|
1264
|
+
type_name = type(cell).__name__
|
|
1265
|
+
raise TypeError(f"cannot set formatting for cells of type {type_name}")
|
|
1266
|
+
|
|
1267
|
+
format = Formatting(type=format_type, **kwargs)
|
|
1268
|
+
format_id = self._model.format_archive(self._table_id, format_type, format)
|
|
1269
|
+
cell._set_formatting(format_id, format_type)
|