tdrpa.tdworker 1.2.13.2__py312-none-win_amd64.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.
- tdrpa/_tdxlwings/__init__.py +193 -0
- tdrpa/_tdxlwings/__pycache__/__init__.cpython-311.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/__init__.cpython-38.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/_win32patch.cpython-311.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/_win32patch.cpython-38.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/_xlwindows.cpython-311.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/_xlwindows.cpython-38.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/apps.cpython-311.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/apps.cpython-38.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/base_classes.cpython-311.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/base_classes.cpython-38.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/com_server.cpython-311.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/com_server.cpython-38.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/constants.cpython-311.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/constants.cpython-38.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/expansion.cpython-311.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/expansion.cpython-38.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/main.cpython-311.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/main.cpython-38.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/udfs.cpython-311.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/udfs.cpython-38.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/utils.cpython-311.pyc +0 -0
- tdrpa/_tdxlwings/__pycache__/utils.cpython-38.pyc +0 -0
- tdrpa/_tdxlwings/_win32patch.py +90 -0
- tdrpa/_tdxlwings/_xlmac.py +2240 -0
- tdrpa/_tdxlwings/_xlwindows.py +2518 -0
- tdrpa/_tdxlwings/addin/Dictionary.cls +474 -0
- tdrpa/_tdxlwings/addin/IWebAuthenticator.cls +71 -0
- tdrpa/_tdxlwings/addin/WebClient.cls +772 -0
- tdrpa/_tdxlwings/addin/WebHelpers.bas +3203 -0
- tdrpa/_tdxlwings/addin/WebRequest.cls +875 -0
- tdrpa/_tdxlwings/addin/WebResponse.cls +453 -0
- tdrpa/_tdxlwings/addin/xlwings.xlam +0 -0
- tdrpa/_tdxlwings/apps.py +35 -0
- tdrpa/_tdxlwings/base_classes.py +1092 -0
- tdrpa/_tdxlwings/cli.py +1306 -0
- tdrpa/_tdxlwings/com_server.py +385 -0
- tdrpa/_tdxlwings/constants.py +3080 -0
- tdrpa/_tdxlwings/conversion/__init__.py +103 -0
- tdrpa/_tdxlwings/conversion/framework.py +147 -0
- tdrpa/_tdxlwings/conversion/numpy_conv.py +34 -0
- tdrpa/_tdxlwings/conversion/pandas_conv.py +184 -0
- tdrpa/_tdxlwings/conversion/standard.py +321 -0
- tdrpa/_tdxlwings/expansion.py +83 -0
- tdrpa/_tdxlwings/ext/__init__.py +3 -0
- tdrpa/_tdxlwings/ext/sql.py +73 -0
- tdrpa/_tdxlwings/html/xlwings-alert.html +71 -0
- tdrpa/_tdxlwings/js/xlwings.js +577 -0
- tdrpa/_tdxlwings/js/xlwings.ts +729 -0
- tdrpa/_tdxlwings/mac_dict.py +6399 -0
- tdrpa/_tdxlwings/main.py +5205 -0
- tdrpa/_tdxlwings/mistune/__init__.py +63 -0
- tdrpa/_tdxlwings/mistune/block_parser.py +366 -0
- tdrpa/_tdxlwings/mistune/inline_parser.py +216 -0
- tdrpa/_tdxlwings/mistune/markdown.py +84 -0
- tdrpa/_tdxlwings/mistune/renderers.py +220 -0
- tdrpa/_tdxlwings/mistune/scanner.py +121 -0
- tdrpa/_tdxlwings/mistune/util.py +41 -0
- tdrpa/_tdxlwings/pro/__init__.py +40 -0
- tdrpa/_tdxlwings/pro/_xlcalamine.py +536 -0
- tdrpa/_tdxlwings/pro/_xlofficejs.py +146 -0
- tdrpa/_tdxlwings/pro/_xlremote.py +1293 -0
- tdrpa/_tdxlwings/pro/custom_functions_code.js +150 -0
- tdrpa/_tdxlwings/pro/embedded_code.py +60 -0
- tdrpa/_tdxlwings/pro/udfs_officejs.py +549 -0
- tdrpa/_tdxlwings/pro/utils.py +199 -0
- tdrpa/_tdxlwings/quickstart.xlsm +0 -0
- tdrpa/_tdxlwings/quickstart_addin.xlam +0 -0
- tdrpa/_tdxlwings/quickstart_addin_ribbon.xlam +0 -0
- tdrpa/_tdxlwings/quickstart_fastapi/main.py +47 -0
- tdrpa/_tdxlwings/quickstart_fastapi/requirements.txt +3 -0
- tdrpa/_tdxlwings/quickstart_standalone.xlsm +0 -0
- tdrpa/_tdxlwings/reports.py +12 -0
- tdrpa/_tdxlwings/rest/__init__.py +1 -0
- tdrpa/_tdxlwings/rest/api.py +368 -0
- tdrpa/_tdxlwings/rest/serializers.py +103 -0
- tdrpa/_tdxlwings/server.py +14 -0
- tdrpa/_tdxlwings/udfs.py +775 -0
- tdrpa/_tdxlwings/utils.py +777 -0
- tdrpa/_tdxlwings/xlwings-0.31.6.applescript +30 -0
- tdrpa/_tdxlwings/xlwings.bas +2061 -0
- tdrpa/_tdxlwings/xlwings_custom_addin.bas +2042 -0
- tdrpa/_tdxlwings/xlwingslib.cp38-win_amd64.pyd +0 -0
- tdrpa/tdworker/__init__.pyi +12 -0
- tdrpa/tdworker/_clip.pyi +50 -0
- tdrpa/tdworker/_excel.pyi +743 -0
- tdrpa/tdworker/_file.pyi +77 -0
- tdrpa/tdworker/_img.pyi +226 -0
- tdrpa/tdworker/_network.pyi +94 -0
- tdrpa/tdworker/_os.pyi +47 -0
- tdrpa/tdworker/_sp.pyi +21 -0
- tdrpa/tdworker/_w.pyi +129 -0
- tdrpa/tdworker/_web.pyi +995 -0
- tdrpa/tdworker/_winE.pyi +228 -0
- tdrpa/tdworker/_winK.pyi +74 -0
- tdrpa/tdworker/_winM.pyi +117 -0
- tdrpa/tdworker.cp312-win_amd64.pyd +0 -0
- tdrpa_tdworker-1.2.13.2.dist-info/METADATA +38 -0
- tdrpa_tdworker-1.2.13.2.dist-info/RECORD +101 -0
- tdrpa_tdworker-1.2.13.2.dist-info/WHEEL +5 -0
- tdrpa_tdworker-1.2.13.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1293 @@
|
|
1
|
+
"""
|
2
|
+
Required Notice: Copyright (C) Zoomer Analytics GmbH.
|
3
|
+
|
4
|
+
xlwings PRO is dual-licensed under one of the following licenses:
|
5
|
+
|
6
|
+
* PolyForm Noncommercial License 1.0.0 (for noncommercial use):
|
7
|
+
https://polyformproject.org/licenses/noncommercial/1.0.0
|
8
|
+
* xlwings PRO License (for commercial use):
|
9
|
+
https://github.com/xlwings/xlwings/blob/main/LICENSE_PRO.txt
|
10
|
+
|
11
|
+
Commercial licenses can be purchased at https://www.xlwings.org
|
12
|
+
"""
|
13
|
+
|
14
|
+
import base64
|
15
|
+
import datetime as dt
|
16
|
+
import numbers
|
17
|
+
import re
|
18
|
+
from functools import lru_cache
|
19
|
+
|
20
|
+
try:
|
21
|
+
import numpy as np
|
22
|
+
except ImportError:
|
23
|
+
np = None
|
24
|
+
try:
|
25
|
+
import pandas as pd
|
26
|
+
except ImportError:
|
27
|
+
pd = None
|
28
|
+
|
29
|
+
from .. import NoSuchObjectError, XlwingsError, __version__, base_classes, utils
|
30
|
+
|
31
|
+
# Time types (doesn't contain dt.date)
|
32
|
+
time_types = (dt.datetime,)
|
33
|
+
if np:
|
34
|
+
time_types = time_types + (np.datetime64,)
|
35
|
+
|
36
|
+
datetime_pattern = r"^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(\.[0-9]+)?(Z|[+-](?:2[0-3]|[01][0-9]):[0-5][0-9])?$" # noqa: E501
|
37
|
+
datetime_regex = re.compile(datetime_pattern)
|
38
|
+
|
39
|
+
|
40
|
+
def _clean_value_data_element(
|
41
|
+
value, datetime_builder, empty_as, number_builder, err_to_str
|
42
|
+
):
|
43
|
+
if value == "":
|
44
|
+
return empty_as
|
45
|
+
if isinstance(value, str):
|
46
|
+
# TODO: Send arrays back and forth with indices of the location of dt values
|
47
|
+
if datetime_regex.match(value):
|
48
|
+
value = dt.datetime.fromisoformat(
|
49
|
+
value[:-1]
|
50
|
+
) # cut off "Z" (Python doesn't accept it and Excel doesn't support tz)
|
51
|
+
elif not err_to_str and value in [
|
52
|
+
"#DIV/0!",
|
53
|
+
"#N/A",
|
54
|
+
"#NAME?",
|
55
|
+
"#NULL!",
|
56
|
+
"#NUM!",
|
57
|
+
"#REF!",
|
58
|
+
"#VALUE!",
|
59
|
+
"#DATA!",
|
60
|
+
]:
|
61
|
+
value = None
|
62
|
+
else:
|
63
|
+
value = value
|
64
|
+
if isinstance(value, dt.datetime) and datetime_builder is not dt.datetime:
|
65
|
+
value = datetime_builder(
|
66
|
+
month=value.month,
|
67
|
+
day=value.day,
|
68
|
+
year=value.year,
|
69
|
+
hour=value.hour,
|
70
|
+
minute=value.minute,
|
71
|
+
second=value.second,
|
72
|
+
microsecond=value.microsecond,
|
73
|
+
tzinfo=None,
|
74
|
+
)
|
75
|
+
elif number_builder is not None and isinstance(value, float):
|
76
|
+
value = number_builder(value)
|
77
|
+
return value
|
78
|
+
|
79
|
+
|
80
|
+
class Engine:
|
81
|
+
def __init__(self):
|
82
|
+
self.apps = Apps()
|
83
|
+
|
84
|
+
@staticmethod
|
85
|
+
def clean_value_data(data, datetime_builder, empty_as, number_builder, err_to_str):
|
86
|
+
return [
|
87
|
+
[
|
88
|
+
_clean_value_data_element(
|
89
|
+
c, datetime_builder, empty_as, number_builder, err_to_str
|
90
|
+
)
|
91
|
+
for c in row
|
92
|
+
]
|
93
|
+
for row in data
|
94
|
+
]
|
95
|
+
|
96
|
+
@staticmethod
|
97
|
+
def prepare_xl_data_element(x, options):
|
98
|
+
if x is None:
|
99
|
+
return ""
|
100
|
+
elif pd and pd.isna(x):
|
101
|
+
return ""
|
102
|
+
elif np and isinstance(x, (np.floating, float)) and np.isnan(x):
|
103
|
+
return ""
|
104
|
+
elif np and isinstance(x, np.number):
|
105
|
+
return float(x)
|
106
|
+
elif np and isinstance(x, np.datetime64):
|
107
|
+
return utils.np_datetime_to_datetime(x).replace(tzinfo=None).isoformat()
|
108
|
+
elif pd and isinstance(x, pd.Timestamp):
|
109
|
+
return x.to_pydatetime().replace(tzinfo=None).isoformat()
|
110
|
+
elif pd and isinstance(x, type(pd.NaT)):
|
111
|
+
return None
|
112
|
+
elif isinstance(x, time_types):
|
113
|
+
x = x.replace(tzinfo=None).isoformat()
|
114
|
+
elif isinstance(x, dt.date):
|
115
|
+
# JS applies tz conversion with "2021-01-01" when calling
|
116
|
+
# toLocaleDateString() while it leaves "2021-01-01T00:00:00" unchanged
|
117
|
+
x = dt.datetime(x.year, x.month, x.day).isoformat()
|
118
|
+
return x
|
119
|
+
|
120
|
+
@property
|
121
|
+
def name(self):
|
122
|
+
return "remote"
|
123
|
+
|
124
|
+
@property
|
125
|
+
def type(self):
|
126
|
+
return "remote"
|
127
|
+
|
128
|
+
|
129
|
+
class Apps(base_classes.Apps):
|
130
|
+
def __init__(self):
|
131
|
+
self._apps = [App(self)]
|
132
|
+
|
133
|
+
def __iter__(self):
|
134
|
+
return iter(self._apps)
|
135
|
+
|
136
|
+
def __len__(self):
|
137
|
+
return len(self._apps)
|
138
|
+
|
139
|
+
def __getitem__(self, index):
|
140
|
+
return self._apps[index]
|
141
|
+
|
142
|
+
def add(self, **kwargs):
|
143
|
+
self._apps.insert(0, App(self, **kwargs))
|
144
|
+
return self._apps[0]
|
145
|
+
|
146
|
+
|
147
|
+
class App(base_classes.App):
|
148
|
+
_next_pid = -1
|
149
|
+
|
150
|
+
def __init__(self, apps, add_book=True, **kwargs):
|
151
|
+
self.apps = apps
|
152
|
+
self._pid = App._next_pid
|
153
|
+
App._next_pid -= 1
|
154
|
+
self._books = Books(self)
|
155
|
+
if add_book:
|
156
|
+
self._books.add()
|
157
|
+
|
158
|
+
def kill(self):
|
159
|
+
self.apps._apps.remove(self)
|
160
|
+
self.apps = None
|
161
|
+
|
162
|
+
@property
|
163
|
+
def engine(self):
|
164
|
+
return engine
|
165
|
+
|
166
|
+
@property
|
167
|
+
def books(self):
|
168
|
+
return self._books
|
169
|
+
|
170
|
+
@property
|
171
|
+
def pid(self):
|
172
|
+
return self._pid
|
173
|
+
|
174
|
+
@property
|
175
|
+
def selection(self):
|
176
|
+
book = self.books.active
|
177
|
+
return Range(sheet=book.sheets.active, arg1=book.api["book"]["selection"])
|
178
|
+
|
179
|
+
@property
|
180
|
+
def visible(self):
|
181
|
+
return True
|
182
|
+
|
183
|
+
@visible.setter
|
184
|
+
def visible(self, value):
|
185
|
+
pass
|
186
|
+
|
187
|
+
def activate(self, steal_focus=None):
|
188
|
+
pass
|
189
|
+
|
190
|
+
def alert(self, prompt, title, buttons, mode, callback):
|
191
|
+
self.books.active.append_json_action(
|
192
|
+
func="alert",
|
193
|
+
args=[
|
194
|
+
"" if prompt is None else prompt,
|
195
|
+
"" if title is None else title,
|
196
|
+
"" if buttons is None else buttons,
|
197
|
+
"" if mode is None else mode,
|
198
|
+
"" if callback is None else callback,
|
199
|
+
],
|
200
|
+
)
|
201
|
+
|
202
|
+
def run(self, macro, args):
|
203
|
+
self.books.active.append_json_action(
|
204
|
+
func="runMacro",
|
205
|
+
args=[macro] + [args] if not isinstance(args, list) else [macro] + args,
|
206
|
+
)
|
207
|
+
|
208
|
+
|
209
|
+
class Books(base_classes.Books):
|
210
|
+
def __init__(self, app):
|
211
|
+
self.app = app
|
212
|
+
self.books = []
|
213
|
+
self._active = None
|
214
|
+
|
215
|
+
@property
|
216
|
+
def active(self):
|
217
|
+
return self._active
|
218
|
+
|
219
|
+
def open(self, json):
|
220
|
+
book = Book(api=json, books=self)
|
221
|
+
self.books.append(book)
|
222
|
+
self._active = book
|
223
|
+
return book
|
224
|
+
|
225
|
+
def add(self):
|
226
|
+
book = Book(
|
227
|
+
api={
|
228
|
+
"version": __version__,
|
229
|
+
"book": {"name": f"Book{len(self) + 1}", "active_sheet_index": 0},
|
230
|
+
"sheets": [
|
231
|
+
{
|
232
|
+
"name": "Sheet1",
|
233
|
+
"values": [[]],
|
234
|
+
},
|
235
|
+
],
|
236
|
+
},
|
237
|
+
books=self,
|
238
|
+
)
|
239
|
+
self.books.append(book)
|
240
|
+
self._active = book
|
241
|
+
return book
|
242
|
+
|
243
|
+
def _try_find_book_by_name(self, name):
|
244
|
+
for book in self.books:
|
245
|
+
if book.name == name or book.fullname == name:
|
246
|
+
return book
|
247
|
+
return None
|
248
|
+
|
249
|
+
def __len__(self):
|
250
|
+
return len(self.books)
|
251
|
+
|
252
|
+
def __iter__(self):
|
253
|
+
for book in self.books:
|
254
|
+
yield book
|
255
|
+
|
256
|
+
def __call__(self, name_or_index):
|
257
|
+
if isinstance(name_or_index, numbers.Number):
|
258
|
+
return self.books[name_or_index - 1]
|
259
|
+
else:
|
260
|
+
book = self._try_find_book_by_name(name_or_index)
|
261
|
+
if book is None:
|
262
|
+
raise KeyError(name_or_index)
|
263
|
+
return book
|
264
|
+
|
265
|
+
|
266
|
+
class Book(base_classes.Book):
|
267
|
+
def __init__(self, api, books):
|
268
|
+
self.books = books
|
269
|
+
self._api = api
|
270
|
+
self._json = {"actions": []}
|
271
|
+
if api["version"] != __version__:
|
272
|
+
raise XlwingsError(
|
273
|
+
f'Your xlwings version is different on the client ({api["version"]}) '
|
274
|
+
f"and server ({__version__})."
|
275
|
+
)
|
276
|
+
|
277
|
+
def append_json_action(self, **kwargs):
|
278
|
+
args = kwargs.get("args")
|
279
|
+
self._json["actions"].append(
|
280
|
+
{
|
281
|
+
"func": kwargs.get("func"),
|
282
|
+
"args": [args] if not isinstance(args, list) else args,
|
283
|
+
"values": kwargs.get("values"),
|
284
|
+
"sheet_position": kwargs.get("sheet_position"),
|
285
|
+
"start_row": kwargs.get("start_row"),
|
286
|
+
"start_column": kwargs.get("start_column"),
|
287
|
+
"row_count": kwargs.get("row_count"),
|
288
|
+
"column_count": kwargs.get("column_count"),
|
289
|
+
}
|
290
|
+
)
|
291
|
+
|
292
|
+
@property
|
293
|
+
def api(self):
|
294
|
+
return self._api
|
295
|
+
|
296
|
+
def json(self):
|
297
|
+
return self._json
|
298
|
+
|
299
|
+
@property
|
300
|
+
def name(self):
|
301
|
+
return self.api["book"]["name"]
|
302
|
+
|
303
|
+
@property
|
304
|
+
def fullname(self):
|
305
|
+
return self.name
|
306
|
+
|
307
|
+
@property
|
308
|
+
def names(self):
|
309
|
+
return Names(parent=self, api=self.api["names"])
|
310
|
+
|
311
|
+
@property
|
312
|
+
def sheets(self):
|
313
|
+
return Sheets(api=self.api["sheets"], book=self)
|
314
|
+
|
315
|
+
@property
|
316
|
+
def app(self):
|
317
|
+
return self.books.app
|
318
|
+
|
319
|
+
def close(self):
|
320
|
+
assert self.api is not None, "Seems this book was already closed."
|
321
|
+
self.books.books.remove(self)
|
322
|
+
self.books = None
|
323
|
+
self._api = None
|
324
|
+
|
325
|
+
def activate(self):
|
326
|
+
pass
|
327
|
+
|
328
|
+
|
329
|
+
class Sheets(base_classes.Sheets):
|
330
|
+
def __init__(self, api, book):
|
331
|
+
self._api = api
|
332
|
+
self.book = book
|
333
|
+
|
334
|
+
@property
|
335
|
+
def active(self):
|
336
|
+
ix = self.book.api["book"]["active_sheet_index"]
|
337
|
+
return Sheet(api=self.api[ix], sheets=self, index=ix + 1)
|
338
|
+
|
339
|
+
@property
|
340
|
+
def api(self):
|
341
|
+
return self._api
|
342
|
+
|
343
|
+
def __call__(self, name_or_index):
|
344
|
+
if isinstance(name_or_index, int):
|
345
|
+
return Sheet(
|
346
|
+
api=self.api[name_or_index - 1], sheets=self, index=name_or_index
|
347
|
+
)
|
348
|
+
else:
|
349
|
+
for ix, sheet in enumerate(self.api):
|
350
|
+
if sheet["name"] == name_or_index:
|
351
|
+
return Sheet(api=sheet, sheets=self, index=ix + 1)
|
352
|
+
raise ValueError(f"Sheet '{name_or_index}' doesn't exist!")
|
353
|
+
|
354
|
+
def add(self, before=None, after=None, name=None):
|
355
|
+
# Default naming logic is different from Desktop apps!
|
356
|
+
sheet_number = 1
|
357
|
+
while True:
|
358
|
+
if f"Sheet{sheet_number}" in [sheet.name for sheet in self]:
|
359
|
+
sheet_number += 1
|
360
|
+
else:
|
361
|
+
break
|
362
|
+
api = {
|
363
|
+
"name": f"Sheet{sheet_number}",
|
364
|
+
"values": [[]],
|
365
|
+
"pictures": [],
|
366
|
+
"tables": [],
|
367
|
+
}
|
368
|
+
if before:
|
369
|
+
if before.index == 1:
|
370
|
+
ix = 1
|
371
|
+
else:
|
372
|
+
ix = before.index - 1
|
373
|
+
elif after:
|
374
|
+
ix = after.index + 1
|
375
|
+
else:
|
376
|
+
# Default position is different from Desktop apps!
|
377
|
+
ix = len(self) + 1
|
378
|
+
self.api.insert(ix - 1, api)
|
379
|
+
self.book.append_json_action(func="addSheet", args=[ix - 1, name])
|
380
|
+
self.book.api["book"]["active_sheet_index"] = ix - 1
|
381
|
+
|
382
|
+
return Sheet(api=api, sheets=self, index=ix)
|
383
|
+
|
384
|
+
def __len__(self):
|
385
|
+
return len(self.api)
|
386
|
+
|
387
|
+
def __iter__(self):
|
388
|
+
for ix, sheet in enumerate(self.api):
|
389
|
+
yield Sheet(api=sheet, sheets=self, index=ix + 1)
|
390
|
+
|
391
|
+
|
392
|
+
class Sheet(base_classes.Sheet):
|
393
|
+
def __init__(self, api, sheets, index):
|
394
|
+
self._api = api
|
395
|
+
self._index = index
|
396
|
+
self.sheets = sheets
|
397
|
+
|
398
|
+
def append_json_action(self, **kwargs):
|
399
|
+
self.book.append_json_action(
|
400
|
+
**{
|
401
|
+
**kwargs,
|
402
|
+
**{
|
403
|
+
"sheet_position": self.index - 1,
|
404
|
+
},
|
405
|
+
}
|
406
|
+
)
|
407
|
+
|
408
|
+
@property
|
409
|
+
def api(self):
|
410
|
+
return self._api
|
411
|
+
|
412
|
+
@property
|
413
|
+
def name(self):
|
414
|
+
return self.api["name"]
|
415
|
+
|
416
|
+
@name.setter
|
417
|
+
def name(self, value):
|
418
|
+
self.append_json_action(
|
419
|
+
func="setSheetName",
|
420
|
+
args=value,
|
421
|
+
)
|
422
|
+
self.api["name"] = value
|
423
|
+
|
424
|
+
@property
|
425
|
+
def index(self):
|
426
|
+
return self._index
|
427
|
+
|
428
|
+
@property
|
429
|
+
def book(self):
|
430
|
+
return self.sheets.book
|
431
|
+
|
432
|
+
def range(self, arg1, arg2=None):
|
433
|
+
return Range(sheet=self, arg1=arg1, arg2=arg2)
|
434
|
+
|
435
|
+
@property
|
436
|
+
def cells(self):
|
437
|
+
return Range(
|
438
|
+
sheet=self,
|
439
|
+
arg1=(1, 1),
|
440
|
+
arg2=(1_048_576, 16_384),
|
441
|
+
)
|
442
|
+
|
443
|
+
@property
|
444
|
+
def names(self):
|
445
|
+
api = [
|
446
|
+
name
|
447
|
+
for name in self.book.api["names"]
|
448
|
+
if name["scope_sheet_index"] is not None
|
449
|
+
and name["scope_sheet_index"] + 1 == self.index
|
450
|
+
and not name["book_scope"]
|
451
|
+
]
|
452
|
+
return Names(parent=self, api=api)
|
453
|
+
|
454
|
+
def activate(self):
|
455
|
+
ix = self.index - 1
|
456
|
+
self.book.api["book"]["active_sheet_index"] = ix
|
457
|
+
self.append_json_action(func="activateSheet", args=ix)
|
458
|
+
|
459
|
+
@property
|
460
|
+
def pictures(self):
|
461
|
+
return Pictures(self)
|
462
|
+
|
463
|
+
@property
|
464
|
+
def tables(self):
|
465
|
+
return Tables(parent=self)
|
466
|
+
|
467
|
+
def delete(self):
|
468
|
+
del self.book.api["sheets"][self.index - 1]
|
469
|
+
self.append_json_action(func="sheetDelete")
|
470
|
+
|
471
|
+
def clear(self):
|
472
|
+
self.append_json_action(func="sheetClear")
|
473
|
+
|
474
|
+
def clear_contents(self):
|
475
|
+
self.append_json_action(func="sheetClearContents")
|
476
|
+
|
477
|
+
def clear_formats(self):
|
478
|
+
self.append_json_action(func="sheetClearFormats")
|
479
|
+
|
480
|
+
|
481
|
+
@lru_cache(None)
|
482
|
+
def get_range_api(api_values, arg1, arg2=None):
|
483
|
+
# Keeping this outside of the Range class allows us to cache it across multiple
|
484
|
+
# instances of the same range
|
485
|
+
if arg2:
|
486
|
+
values = [
|
487
|
+
row[arg1[1] - 1 : arg2[1]] for row in api_values[arg1[0] - 1 : arg2[0]]
|
488
|
+
]
|
489
|
+
if not values:
|
490
|
+
# Completely outside the used range
|
491
|
+
return [(None,) * (arg2[1] + 1 - arg1[1])] * (arg2[0] + 1 - arg1[0])
|
492
|
+
else:
|
493
|
+
# Partly outside the used range
|
494
|
+
nrows = arg2[0] + 1 - arg1[0]
|
495
|
+
ncols = arg2[1] + 1 - arg1[1]
|
496
|
+
nrows_actual = len(values)
|
497
|
+
ncols_actual = len(values[0])
|
498
|
+
delta_rows = nrows - nrows_actual
|
499
|
+
delta_cols = ncols - ncols_actual
|
500
|
+
if delta_rows != 0:
|
501
|
+
values.extend([(None,) * ncols_actual] * delta_rows)
|
502
|
+
if delta_cols != 0:
|
503
|
+
v = []
|
504
|
+
for row in values:
|
505
|
+
v.append(row + (None,) * delta_cols)
|
506
|
+
values = v
|
507
|
+
return values
|
508
|
+
else:
|
509
|
+
try:
|
510
|
+
values = [(api_values[arg1[0] - 1][arg1[1] - 1],)]
|
511
|
+
return values
|
512
|
+
except IndexError:
|
513
|
+
# Outside the used range
|
514
|
+
return [(None,)]
|
515
|
+
|
516
|
+
|
517
|
+
class Range(base_classes.Range):
|
518
|
+
def __init__(self, sheet, arg1, arg2=None):
|
519
|
+
self.sheet = sheet
|
520
|
+
|
521
|
+
# Range
|
522
|
+
if isinstance(arg1, Range) and isinstance(arg2, Range):
|
523
|
+
cell1 = arg1.coords[1], arg1.coords[2]
|
524
|
+
cell2 = arg2.coords[1], arg2.coords[2]
|
525
|
+
arg1 = min(cell1[0], cell2[0]), min(cell1[1], cell2[1])
|
526
|
+
arg2 = max(cell1[0], cell2[0]), max(cell1[1], cell2[1])
|
527
|
+
# A1 notation
|
528
|
+
if isinstance(arg1, str):
|
529
|
+
# A1 notation
|
530
|
+
tuple1, tuple2 = utils.a1_to_tuples(arg1)
|
531
|
+
if not tuple1:
|
532
|
+
# Named range
|
533
|
+
for api_name in sheet.book.api["names"]:
|
534
|
+
if (
|
535
|
+
api_name["name"].split("!")[-1] == arg1
|
536
|
+
and api_name["sheet_index"] == sheet.index - 1
|
537
|
+
):
|
538
|
+
tuple1, tuple2 = utils.a1_to_tuples(api_name["address"])
|
539
|
+
break
|
540
|
+
if not tuple1:
|
541
|
+
# Tables
|
542
|
+
for api_table in sheet.api["tables"]:
|
543
|
+
if api_table["name"] == arg1:
|
544
|
+
tuple1, tuple2 = utils.a1_to_tuples(
|
545
|
+
api_table["data_body_range_address"]
|
546
|
+
)
|
547
|
+
break
|
548
|
+
if not tuple1:
|
549
|
+
raise NoSuchObjectError(
|
550
|
+
f"The address/named range '{arg1}' doesn't exist."
|
551
|
+
)
|
552
|
+
arg1, arg2 = tuple1, tuple2
|
553
|
+
|
554
|
+
# Coordinates
|
555
|
+
if len(arg1) == 4:
|
556
|
+
row, col, nrows, ncols = arg1
|
557
|
+
arg1 = (row, col)
|
558
|
+
if nrows > 1 or ncols > 1:
|
559
|
+
arg2 = (row + nrows - 1, col + ncols - 1)
|
560
|
+
|
561
|
+
self.arg1 = arg1 # 1-based tuple
|
562
|
+
self.arg2 = arg2 # 1-based tuple
|
563
|
+
self.sheet = sheet
|
564
|
+
|
565
|
+
def append_json_action(self, **kwargs):
|
566
|
+
nrows, ncols = self.shape
|
567
|
+
self.sheet.book.append_json_action(
|
568
|
+
**{
|
569
|
+
**kwargs,
|
570
|
+
**{
|
571
|
+
"sheet_position": self.sheet.index - 1,
|
572
|
+
"start_row": self.row - 1,
|
573
|
+
"start_column": self.column - 1,
|
574
|
+
"row_count": nrows,
|
575
|
+
"column_count": ncols,
|
576
|
+
},
|
577
|
+
}
|
578
|
+
)
|
579
|
+
|
580
|
+
@property
|
581
|
+
def api(self):
|
582
|
+
return get_range_api(
|
583
|
+
tuple(tuple(row) for row in self.sheet.api["values"]), self.arg1, self.arg2
|
584
|
+
)
|
585
|
+
|
586
|
+
@property
|
587
|
+
def coords(self):
|
588
|
+
return self.sheet.name, self.row, self.column, len(self.api), len(self.api[0])
|
589
|
+
|
590
|
+
@property
|
591
|
+
def row(self):
|
592
|
+
return self.arg1[0]
|
593
|
+
|
594
|
+
@property
|
595
|
+
def column(self):
|
596
|
+
return self.arg1[1]
|
597
|
+
|
598
|
+
@property
|
599
|
+
def shape(self):
|
600
|
+
if self.arg2:
|
601
|
+
return self.arg2[0] - self.arg1[0] + 1, self.arg2[1] - self.arg1[1] + 1
|
602
|
+
else:
|
603
|
+
return 1, 1
|
604
|
+
|
605
|
+
@property
|
606
|
+
def raw_value(self):
|
607
|
+
return self.api
|
608
|
+
|
609
|
+
@raw_value.setter
|
610
|
+
def raw_value(self, value):
|
611
|
+
if not isinstance(value, list):
|
612
|
+
# Covers also this case: myrange['A1:B2'].value = 'xyz'
|
613
|
+
nrows, ncols = self.shape
|
614
|
+
values = [[value] * ncols] * nrows
|
615
|
+
else:
|
616
|
+
values = value
|
617
|
+
self.append_json_action(
|
618
|
+
func="setValues",
|
619
|
+
values=values,
|
620
|
+
)
|
621
|
+
|
622
|
+
def clear_contents(self):
|
623
|
+
self.append_json_action(
|
624
|
+
func="rangeClearContents",
|
625
|
+
)
|
626
|
+
|
627
|
+
def clear(self):
|
628
|
+
self.append_json_action(
|
629
|
+
func="rangeClear",
|
630
|
+
)
|
631
|
+
|
632
|
+
def clear_formats(self):
|
633
|
+
self.append_json_action(
|
634
|
+
func="rangeClearFormats",
|
635
|
+
)
|
636
|
+
|
637
|
+
@property
|
638
|
+
def address(self):
|
639
|
+
nrows, ncols = self.shape
|
640
|
+
address = f"${utils.col_name(self.column)}${self.row}"
|
641
|
+
if nrows == 1 and ncols == 1:
|
642
|
+
return address
|
643
|
+
else:
|
644
|
+
return (
|
645
|
+
f"{address}"
|
646
|
+
f":${utils.col_name(self.column + ncols - 1)}${self.row + nrows - 1}"
|
647
|
+
)
|
648
|
+
|
649
|
+
@property
|
650
|
+
def has_array(self):
|
651
|
+
# Not supported, but since this is only used for legacy CSE arrays, probably
|
652
|
+
# not much of an issue. Here as there's currently a dependency in expansion.py.
|
653
|
+
return None
|
654
|
+
|
655
|
+
def end(self, direction):
|
656
|
+
if direction == "down":
|
657
|
+
i = 1
|
658
|
+
while True:
|
659
|
+
try:
|
660
|
+
if self.sheet.api["values"][self.row - 1 + i][self.column - 1]:
|
661
|
+
i += 1
|
662
|
+
else:
|
663
|
+
break
|
664
|
+
except IndexError:
|
665
|
+
break # outside used range
|
666
|
+
nrows = i - 1
|
667
|
+
return self.sheet.range((self.row + nrows, self.column))
|
668
|
+
if direction == "up":
|
669
|
+
i = -1
|
670
|
+
while True:
|
671
|
+
row_ix = self.row - 1 + i
|
672
|
+
if row_ix >= 0 and self.sheet.api["values"][row_ix][self.column - 1]:
|
673
|
+
i -= 1
|
674
|
+
else:
|
675
|
+
break
|
676
|
+
nrows = i + 1
|
677
|
+
return self.sheet.range((self.row + nrows, self.column))
|
678
|
+
if direction == "right":
|
679
|
+
i = 1
|
680
|
+
while True:
|
681
|
+
try:
|
682
|
+
if self.sheet.api["values"][self.row - 1][self.column - 1 + i]:
|
683
|
+
i += 1
|
684
|
+
else:
|
685
|
+
break
|
686
|
+
except IndexError:
|
687
|
+
break # outside used range
|
688
|
+
ncols = i - 1
|
689
|
+
return self.sheet.range((self.row, self.column + ncols))
|
690
|
+
if direction == "left":
|
691
|
+
i = -1
|
692
|
+
while True:
|
693
|
+
col_ix = self.column - 1 + i
|
694
|
+
if col_ix >= 0 and self.sheet.api["values"][self.row - 1][col_ix]:
|
695
|
+
i -= 1
|
696
|
+
else:
|
697
|
+
break
|
698
|
+
ncols = i + 1
|
699
|
+
return self.sheet.range((self.row, self.column + ncols))
|
700
|
+
|
701
|
+
def autofit(self, axis=None):
|
702
|
+
if axis == "rows" or axis == "r":
|
703
|
+
self.append_json_action(func="setAutofit", args="rows")
|
704
|
+
elif axis == "columns" or axis == "c":
|
705
|
+
self.append_json_action(func="setAutofit", args="columns")
|
706
|
+
elif axis is None:
|
707
|
+
self.append_json_action(func="setAutofit", args="rows")
|
708
|
+
self.append_json_action(func="setAutofit", args="columns")
|
709
|
+
|
710
|
+
@property
|
711
|
+
def color(self):
|
712
|
+
raise NotImplementedError()
|
713
|
+
|
714
|
+
@color.setter
|
715
|
+
def color(self, value):
|
716
|
+
if not isinstance(value, str):
|
717
|
+
raise ValueError('Color must be supplied in hex format e.g., "#FFA500".')
|
718
|
+
self.append_json_action(func="setRangeColor", args=value)
|
719
|
+
|
720
|
+
def add_hyperlink(self, address, text_to_display=None, screen_tip=None):
|
721
|
+
self.append_json_action(
|
722
|
+
func="addHyperlink", args=[address, text_to_display, screen_tip]
|
723
|
+
)
|
724
|
+
|
725
|
+
@property
|
726
|
+
def number_format(self):
|
727
|
+
raise NotImplementedError()
|
728
|
+
|
729
|
+
@number_format.setter
|
730
|
+
def number_format(self, value):
|
731
|
+
self.append_json_action(func="setNumberFormat", args=value)
|
732
|
+
|
733
|
+
@property
|
734
|
+
def name(self):
|
735
|
+
for name in self.sheet.book.api["names"]:
|
736
|
+
if name["sheet_index"] == self.sheet.index - 1 and name[
|
737
|
+
"address"
|
738
|
+
] == self.address.replace("$", ""):
|
739
|
+
return Name(
|
740
|
+
parent=self.sheet.book if name["book_scope"] else self.sheet,
|
741
|
+
api=name,
|
742
|
+
)
|
743
|
+
|
744
|
+
@name.setter
|
745
|
+
def name(self, value):
|
746
|
+
self.append_json_action(
|
747
|
+
func="setRangeName",
|
748
|
+
args=value,
|
749
|
+
)
|
750
|
+
|
751
|
+
def copy(self, destination=None):
|
752
|
+
# TODO: introduce the new copy_from from Office Scripts
|
753
|
+
if destination is None:
|
754
|
+
raise XlwingsError("range.copy() requires a destination argument.")
|
755
|
+
self.append_json_action(
|
756
|
+
func="copyRange",
|
757
|
+
args=[destination.sheet.index - 1, destination.address],
|
758
|
+
)
|
759
|
+
|
760
|
+
def delete(self, shift=None):
|
761
|
+
if shift not in ("up", "left"):
|
762
|
+
# Non-remote version allows shift=None
|
763
|
+
raise XlwingsError(
|
764
|
+
"range.delete() requires either 'up' or 'left' as shift arguments."
|
765
|
+
)
|
766
|
+
self.append_json_action(func="rangeDelete", args=shift)
|
767
|
+
|
768
|
+
def insert(self, shift=None, copy_origin=None):
|
769
|
+
if shift not in ("down", "right"):
|
770
|
+
raise XlwingsError(
|
771
|
+
"range.insert() requires either 'down' or 'right' as shift arguments."
|
772
|
+
)
|
773
|
+
if copy_origin not in (
|
774
|
+
"format_from_left_or_above",
|
775
|
+
"format_from_right_or_below",
|
776
|
+
):
|
777
|
+
raise XlwingsError(
|
778
|
+
"range.insert() requires either 'format_from_left_or_above' or "
|
779
|
+
"'format_from_right_or_below' as copy_origin arguments."
|
780
|
+
)
|
781
|
+
# copy_origin is only supported by VBA clients
|
782
|
+
self.append_json_action(func="rangeInsert", args=[shift, copy_origin])
|
783
|
+
|
784
|
+
def select(self):
|
785
|
+
self.append_json_action(
|
786
|
+
func="rangeSelect",
|
787
|
+
)
|
788
|
+
|
789
|
+
def __len__(self):
|
790
|
+
nrows, ncols = self.shape
|
791
|
+
return nrows * ncols
|
792
|
+
|
793
|
+
def __call__(self, arg1, arg2=None):
|
794
|
+
if arg2 is None:
|
795
|
+
col = (arg1 - 1) % self.shape[1]
|
796
|
+
row = int((arg1 - 1 - col) / self.shape[1])
|
797
|
+
return self(row + 1, col + 1)
|
798
|
+
else:
|
799
|
+
return Range(
|
800
|
+
sheet=self.sheet,
|
801
|
+
arg1=(self.row + arg1 - 1, self.column + arg2 - 1),
|
802
|
+
)
|
803
|
+
|
804
|
+
|
805
|
+
class Collection(base_classes.Collection):
|
806
|
+
def __init__(self, parent):
|
807
|
+
self._parent = parent
|
808
|
+
self._api = parent.api[self._attr]
|
809
|
+
|
810
|
+
@property
|
811
|
+
def api(self):
|
812
|
+
return self._api
|
813
|
+
|
814
|
+
@property
|
815
|
+
def parent(self):
|
816
|
+
return self._parent
|
817
|
+
|
818
|
+
def __call__(self, key):
|
819
|
+
if isinstance(key, numbers.Number):
|
820
|
+
if key > len(self):
|
821
|
+
raise KeyError(key)
|
822
|
+
else:
|
823
|
+
return self._wrap(self.parent, key)
|
824
|
+
else:
|
825
|
+
for ix, i in enumerate(self.api):
|
826
|
+
if i["name"] == key:
|
827
|
+
return self._wrap(self.parent, ix + 1)
|
828
|
+
raise KeyError(key)
|
829
|
+
|
830
|
+
def __len__(self):
|
831
|
+
return len(self.api)
|
832
|
+
|
833
|
+
def __iter__(self):
|
834
|
+
for ix, api in enumerate(self.api):
|
835
|
+
yield self._wrap(self._parent, ix + 1)
|
836
|
+
|
837
|
+
def __contains__(self, key):
|
838
|
+
if isinstance(key, numbers.Number):
|
839
|
+
return 1 <= key <= len(self)
|
840
|
+
else:
|
841
|
+
for i in self.api:
|
842
|
+
if i["name"] == key:
|
843
|
+
return True
|
844
|
+
return False
|
845
|
+
|
846
|
+
|
847
|
+
class Picture(base_classes.Picture):
|
848
|
+
def __init__(self, parent, key):
|
849
|
+
self._parent = parent
|
850
|
+
self._api = self.parent.api["pictures"][key - 1]
|
851
|
+
self.key = key
|
852
|
+
|
853
|
+
def append_json_action(self, **kwargs):
|
854
|
+
self.parent.book.append_json_action(
|
855
|
+
**{
|
856
|
+
**kwargs,
|
857
|
+
**{
|
858
|
+
"sheet_position": self.parent.index - 1,
|
859
|
+
},
|
860
|
+
}
|
861
|
+
)
|
862
|
+
|
863
|
+
@property
|
864
|
+
def api(self):
|
865
|
+
return self._api
|
866
|
+
|
867
|
+
@property
|
868
|
+
def parent(self):
|
869
|
+
return self._parent
|
870
|
+
|
871
|
+
@property
|
872
|
+
def name(self):
|
873
|
+
return self.api["name"]
|
874
|
+
|
875
|
+
@name.setter
|
876
|
+
def name(self, value):
|
877
|
+
self.api["name"] = value
|
878
|
+
self.append_json_action(func="setPictureName", args=[self.index - 1, value])
|
879
|
+
|
880
|
+
@property
|
881
|
+
def width(self):
|
882
|
+
return self.api["width"]
|
883
|
+
|
884
|
+
@width.setter
|
885
|
+
def width(self, value):
|
886
|
+
self.append_json_action(func="setPictureWidth", args=[self.index - 1, value])
|
887
|
+
|
888
|
+
@property
|
889
|
+
def height(self):
|
890
|
+
return self.api["height"]
|
891
|
+
|
892
|
+
@height.setter
|
893
|
+
def height(self, value):
|
894
|
+
self.append_json_action(func="setPictureHeight", args=[self.index - 1, value])
|
895
|
+
|
896
|
+
@property
|
897
|
+
def index(self):
|
898
|
+
# TODO: make available in public API
|
899
|
+
if isinstance(self.key, numbers.Number):
|
900
|
+
return self.key
|
901
|
+
else:
|
902
|
+
for ix, obj in self.api:
|
903
|
+
if obj["name"] == self.key:
|
904
|
+
return ix + 1
|
905
|
+
raise KeyError(self.key)
|
906
|
+
|
907
|
+
def delete(self):
|
908
|
+
self.parent._api["pictures"].pop(self.index - 1)
|
909
|
+
self.append_json_action(func="deletePicture", args=self.index - 1)
|
910
|
+
|
911
|
+
def update(self, filename):
|
912
|
+
with open(filename, "rb") as image_file:
|
913
|
+
encoded_image_string = base64.b64encode(image_file.read()).decode("utf-8")
|
914
|
+
self.append_json_action(
|
915
|
+
func="updatePicture",
|
916
|
+
args=[
|
917
|
+
encoded_image_string,
|
918
|
+
self.index - 1,
|
919
|
+
self.name,
|
920
|
+
self.width,
|
921
|
+
self.height,
|
922
|
+
],
|
923
|
+
)
|
924
|
+
return self
|
925
|
+
|
926
|
+
|
927
|
+
class Pictures(Collection, base_classes.Pictures):
|
928
|
+
_attr = "pictures"
|
929
|
+
_wrap = Picture
|
930
|
+
|
931
|
+
def append_json_action(self, **kwargs):
|
932
|
+
self.parent.book.append_json_action(
|
933
|
+
**{
|
934
|
+
**kwargs,
|
935
|
+
**{
|
936
|
+
"sheet_position": self.parent.index - 1,
|
937
|
+
},
|
938
|
+
}
|
939
|
+
)
|
940
|
+
|
941
|
+
def add(
|
942
|
+
self,
|
943
|
+
filename,
|
944
|
+
link_to_file=None,
|
945
|
+
save_with_document=None,
|
946
|
+
left=None,
|
947
|
+
top=None,
|
948
|
+
width=None,
|
949
|
+
height=None,
|
950
|
+
anchor=None,
|
951
|
+
):
|
952
|
+
if self.parent.book.api["client"] == "Google Apps Script" and (left or top):
|
953
|
+
raise ValueError(
|
954
|
+
"'left' and 'top' are not supported with Google Sheets. "
|
955
|
+
"Use 'anchor' instead."
|
956
|
+
)
|
957
|
+
if anchor is None:
|
958
|
+
column_index = 0
|
959
|
+
row_index = 0
|
960
|
+
else:
|
961
|
+
column_index = anchor.column - 1
|
962
|
+
row_index = anchor.row - 1
|
963
|
+
# Google Sheets allows a max size of 1 million pixels. For matplotlib, you
|
964
|
+
# can control the pixels like so: fig = plt.figure(figsize=(6, 4), dpi=200)
|
965
|
+
# This sample has (6 * 200) * (4 * 200) = 960,000 px
|
966
|
+
# Note that savefig(bbox_inches="tight") crops the image and therefore will
|
967
|
+
# reduce the number of pixels in a non-deterministic way. Existing figure
|
968
|
+
# size can be checked via fig.get_size_inches(). pandas accepts figsize also:
|
969
|
+
# ax = df.plot(figsize=(3,3))
|
970
|
+
# fig = ax.get_figure()
|
971
|
+
with open(filename, "rb") as image_file:
|
972
|
+
encoded_image_string = base64.b64encode(image_file.read()).decode("utf-8")
|
973
|
+
# TODO: width and height are currently ignored but can be set via obj properties
|
974
|
+
self.append_json_action(
|
975
|
+
func="addPicture",
|
976
|
+
args=[
|
977
|
+
encoded_image_string,
|
978
|
+
column_index,
|
979
|
+
row_index,
|
980
|
+
left if left else 0,
|
981
|
+
top if top else 0,
|
982
|
+
],
|
983
|
+
)
|
984
|
+
self.parent._api["pictures"].append(
|
985
|
+
{"name": "Image", "width": None, "height": None}
|
986
|
+
)
|
987
|
+
return Picture(self.parent, len(self.parent.api["pictures"]))
|
988
|
+
|
989
|
+
|
990
|
+
class Name(base_classes.Name):
|
991
|
+
def __init__(self, parent, api):
|
992
|
+
self.parent = parent
|
993
|
+
self.api = api
|
994
|
+
|
995
|
+
@property
|
996
|
+
def name(self):
|
997
|
+
if self.api["book_scope"]:
|
998
|
+
return self.api["name"]
|
999
|
+
else:
|
1000
|
+
sheet_name = self.api["scope_sheet_name"]
|
1001
|
+
if "!" not in self.api["name"]:
|
1002
|
+
# VBA/Google Sheets already do this
|
1003
|
+
sheet_name = f"'{sheet_name}'" if " " in sheet_name else sheet_name
|
1004
|
+
return f"{sheet_name}!{self.api['name']}"
|
1005
|
+
else:
|
1006
|
+
return self.api["name"]
|
1007
|
+
|
1008
|
+
@property
|
1009
|
+
def refers_to(self):
|
1010
|
+
book = self.parent if isinstance(self.parent, Book) else self.parent.book
|
1011
|
+
sheet = book.sheets(self.api["sheet_index"] + 1)
|
1012
|
+
sheet_name = f"'{sheet.name}'" if " " in sheet.name else sheet.name
|
1013
|
+
return f"={sheet_name}!{sheet.range(self.api['address']).address}"
|
1014
|
+
|
1015
|
+
@property
|
1016
|
+
def refers_to_range(self):
|
1017
|
+
book = self.parent if isinstance(self.parent, Book) else self.parent.book
|
1018
|
+
sheet = book.sheets(self.api["sheet_index"] + 1)
|
1019
|
+
return sheet.range(self.api["address"])
|
1020
|
+
|
1021
|
+
def delete(self):
|
1022
|
+
# TODO: delete in api
|
1023
|
+
self.parent.append_json_action(
|
1024
|
+
func="nameDelete",
|
1025
|
+
args=[
|
1026
|
+
self.name, # this includes the sheet name for sheet scope
|
1027
|
+
self.refers_to,
|
1028
|
+
self.api["name"], # no sheet name
|
1029
|
+
self.api["sheet_index"],
|
1030
|
+
self.api["book_scope"],
|
1031
|
+
self.api["scope_sheet_index"],
|
1032
|
+
],
|
1033
|
+
)
|
1034
|
+
|
1035
|
+
|
1036
|
+
class Names(base_classes.Names):
|
1037
|
+
def __init__(self, parent, api):
|
1038
|
+
self.parent = parent
|
1039
|
+
self.api = api
|
1040
|
+
|
1041
|
+
def add(self, name, refers_to):
|
1042
|
+
# TODO: raise backend error in case of duplicates
|
1043
|
+
if isinstance(self.parent, Book):
|
1044
|
+
is_parent_book = True
|
1045
|
+
else:
|
1046
|
+
is_parent_book = False
|
1047
|
+
self.parent.append_json_action(func="namesAdd", args=[name, refers_to])
|
1048
|
+
|
1049
|
+
def _get_sheet_index(parent):
|
1050
|
+
if is_parent_book:
|
1051
|
+
sheets = parent.sheets
|
1052
|
+
else:
|
1053
|
+
sheets = parent.book.sheets
|
1054
|
+
for sheet in sheets:
|
1055
|
+
if sheet.name == refers_to.split("!")[0].replace("=", "").replace(
|
1056
|
+
"'", ""
|
1057
|
+
):
|
1058
|
+
return sheet.index - 1
|
1059
|
+
|
1060
|
+
return Name(
|
1061
|
+
self.parent,
|
1062
|
+
{
|
1063
|
+
"name": name,
|
1064
|
+
"sheet_index": _get_sheet_index(self.parent),
|
1065
|
+
"address": refers_to.split("!")[1].replace("$", ""),
|
1066
|
+
"book_scope": True if is_parent_book else False,
|
1067
|
+
},
|
1068
|
+
)
|
1069
|
+
|
1070
|
+
def __call__(self, name_or_index):
|
1071
|
+
if isinstance(name_or_index, numbers.Number):
|
1072
|
+
name_or_index -= 1
|
1073
|
+
if name_or_index > len(self):
|
1074
|
+
raise KeyError(name_or_index)
|
1075
|
+
else:
|
1076
|
+
return Name(self.parent, api=self.api[name_or_index])
|
1077
|
+
else:
|
1078
|
+
for ix, i in enumerate(self.api):
|
1079
|
+
name = Name(self.parent, api=self.api[ix])
|
1080
|
+
if name.name == name_or_index:
|
1081
|
+
# Sheet scope names have the sheet name prepended
|
1082
|
+
return name
|
1083
|
+
raise KeyError(name_or_index)
|
1084
|
+
|
1085
|
+
def contains(self, name_or_index):
|
1086
|
+
if isinstance(name_or_index, numbers.Number):
|
1087
|
+
return 1 <= name_or_index <= len(self)
|
1088
|
+
else:
|
1089
|
+
for i in self.api:
|
1090
|
+
if i["name"] == name_or_index:
|
1091
|
+
return True
|
1092
|
+
return False
|
1093
|
+
|
1094
|
+
def __len__(self):
|
1095
|
+
return len(self.api)
|
1096
|
+
|
1097
|
+
|
1098
|
+
engine = Engine()
|
1099
|
+
|
1100
|
+
|
1101
|
+
class Table(base_classes.Table):
|
1102
|
+
@property
|
1103
|
+
def show_autofilter(self):
|
1104
|
+
return self.api["show_autofilter"]
|
1105
|
+
|
1106
|
+
@show_autofilter.setter
|
1107
|
+
def show_autofilter(self, value):
|
1108
|
+
self.append_json_action(
|
1109
|
+
func="showAutofilterTable", args=[self.index - 1, value]
|
1110
|
+
)
|
1111
|
+
|
1112
|
+
def __init__(self, parent, key):
|
1113
|
+
self._parent = parent
|
1114
|
+
self._api = self.parent.api["tables"][key - 1]
|
1115
|
+
self.key = key
|
1116
|
+
|
1117
|
+
def append_json_action(self, **kwargs):
|
1118
|
+
self.parent.book.append_json_action(
|
1119
|
+
**{
|
1120
|
+
**kwargs,
|
1121
|
+
**{
|
1122
|
+
"sheet_position": self.parent.index - 1,
|
1123
|
+
},
|
1124
|
+
}
|
1125
|
+
)
|
1126
|
+
|
1127
|
+
@property
|
1128
|
+
def api(self):
|
1129
|
+
return self._api
|
1130
|
+
|
1131
|
+
@property
|
1132
|
+
def parent(self):
|
1133
|
+
return self._parent
|
1134
|
+
|
1135
|
+
@property
|
1136
|
+
def name(self):
|
1137
|
+
return self.api["name"]
|
1138
|
+
|
1139
|
+
@name.setter
|
1140
|
+
def name(self, value):
|
1141
|
+
self.api["name"] = value
|
1142
|
+
self.append_json_action(func="setTableName", args=[self.index - 1, value])
|
1143
|
+
|
1144
|
+
@property
|
1145
|
+
def range(self):
|
1146
|
+
if self.api["range_address"]:
|
1147
|
+
return self.parent.range(self.api["range_address"])
|
1148
|
+
else:
|
1149
|
+
return None
|
1150
|
+
|
1151
|
+
@property
|
1152
|
+
def header_row_range(self):
|
1153
|
+
if self.api["header_row_range_address"]:
|
1154
|
+
return self.parent.range(self.api["header_row_range_address"])
|
1155
|
+
else:
|
1156
|
+
return None
|
1157
|
+
|
1158
|
+
@property
|
1159
|
+
def data_body_range(self):
|
1160
|
+
if self.api["data_body_range_address"]:
|
1161
|
+
return self.parent.range(self.api["data_body_range_address"])
|
1162
|
+
else:
|
1163
|
+
return None
|
1164
|
+
|
1165
|
+
@property
|
1166
|
+
def totals_row_range(self):
|
1167
|
+
if self.api["total_row_range_address"]:
|
1168
|
+
return self.parent.range(self.api["total_row_range_address"])
|
1169
|
+
else:
|
1170
|
+
return None
|
1171
|
+
|
1172
|
+
@property
|
1173
|
+
def show_headers(self):
|
1174
|
+
return self.api["show_headers"]
|
1175
|
+
|
1176
|
+
@show_headers.setter
|
1177
|
+
def show_headers(self, value):
|
1178
|
+
self.append_json_action(func="showHeadersTable", args=[self.index - 1, value])
|
1179
|
+
|
1180
|
+
@property
|
1181
|
+
def show_totals(self):
|
1182
|
+
return self.api["show_totals"]
|
1183
|
+
|
1184
|
+
@show_totals.setter
|
1185
|
+
def show_totals(self, value):
|
1186
|
+
self.append_json_action(func="showTotalsTable", args=[self.index - 1, value])
|
1187
|
+
|
1188
|
+
@property
|
1189
|
+
def table_style(self):
|
1190
|
+
return self.api["table_style"]
|
1191
|
+
|
1192
|
+
@table_style.setter
|
1193
|
+
def table_style(self, value):
|
1194
|
+
self.append_json_action(func="setTableStyle", args=[self.index - 1, value])
|
1195
|
+
|
1196
|
+
@property
|
1197
|
+
def index(self):
|
1198
|
+
# TODO: make available in public API
|
1199
|
+
if isinstance(self.key, numbers.Number):
|
1200
|
+
return self.key
|
1201
|
+
else:
|
1202
|
+
for ix, obj in self.api:
|
1203
|
+
if obj["name"] == self.key:
|
1204
|
+
return ix + 1
|
1205
|
+
raise KeyError(self.key)
|
1206
|
+
|
1207
|
+
def resize(self, range):
|
1208
|
+
self.append_json_action(
|
1209
|
+
func="resizeTable", args=[self.index - 1, range.address]
|
1210
|
+
)
|
1211
|
+
|
1212
|
+
|
1213
|
+
class Tables(Collection, base_classes.Tables):
|
1214
|
+
_attr = "tables"
|
1215
|
+
_wrap = Table
|
1216
|
+
|
1217
|
+
def append_json_action(self, **kwargs):
|
1218
|
+
self.parent.book.append_json_action(
|
1219
|
+
**{
|
1220
|
+
**kwargs,
|
1221
|
+
**{
|
1222
|
+
"sheet_position": self.parent.index - 1,
|
1223
|
+
},
|
1224
|
+
}
|
1225
|
+
)
|
1226
|
+
|
1227
|
+
def add(
|
1228
|
+
self,
|
1229
|
+
source_type=None,
|
1230
|
+
source=None,
|
1231
|
+
link_source=None,
|
1232
|
+
has_headers=None,
|
1233
|
+
destination=None,
|
1234
|
+
table_style_name=None,
|
1235
|
+
name=None,
|
1236
|
+
):
|
1237
|
+
self.append_json_action(
|
1238
|
+
func="addTable",
|
1239
|
+
args=[source.address, has_headers, table_style_name, name],
|
1240
|
+
)
|
1241
|
+
self.parent._api["tables"].append(
|
1242
|
+
{
|
1243
|
+
"name": "",
|
1244
|
+
"range_address": None,
|
1245
|
+
"header_row_range_address": None,
|
1246
|
+
"data_body_range_address": None,
|
1247
|
+
"total_row_range_address": None,
|
1248
|
+
"show_headers": None,
|
1249
|
+
"show_totals": None,
|
1250
|
+
"table_style": "",
|
1251
|
+
}
|
1252
|
+
)
|
1253
|
+
return Table(self.parent, len(self.parent.api["tables"]))
|
1254
|
+
|
1255
|
+
|
1256
|
+
# if __name__ == "__main__":
|
1257
|
+
# # python -m xlwings.pro._xlremote
|
1258
|
+
# import inspect
|
1259
|
+
#
|
1260
|
+
# def print_unimplemented_attributes(class_name, base_class, derived_class=None):
|
1261
|
+
# if class_name == "Apps":
|
1262
|
+
# return
|
1263
|
+
# base_attributes = set(
|
1264
|
+
# attr
|
1265
|
+
# for attr in vars(base_class)
|
1266
|
+
# if not (attr.startswith("_") or attr == "api")
|
1267
|
+
# )
|
1268
|
+
# if derived_class:
|
1269
|
+
# derived_attributes = set(
|
1270
|
+
# attr for attr in vars(derived_class) if not attr.startswith("_")
|
1271
|
+
# )
|
1272
|
+
# else:
|
1273
|
+
# derived_attributes = set()
|
1274
|
+
# unimplemented_attributes = base_attributes - derived_attributes
|
1275
|
+
#
|
1276
|
+
# if unimplemented_attributes:
|
1277
|
+
# print("")
|
1278
|
+
# print(f" xlwings.{class_name}")
|
1279
|
+
# print("")
|
1280
|
+
# for attribute in unimplemented_attributes:
|
1281
|
+
# if not attribute.startswith("__") and attribute not in (
|
1282
|
+
# "api",
|
1283
|
+
# "xl",
|
1284
|
+
# "hwnd",
|
1285
|
+
# ):
|
1286
|
+
# if callable(getattr(base_class, attribute)):
|
1287
|
+
# print(f" - {attribute}()")
|
1288
|
+
# else:
|
1289
|
+
# print(f" - {attribute}")
|
1290
|
+
#
|
1291
|
+
# for name, obj in inspect.getmembers(base_classes):
|
1292
|
+
# if inspect.isclass(obj):
|
1293
|
+
# print_unimplemented_attributes(name, obj, globals().get(name))
|