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.
Files changed (101) hide show
  1. tdrpa/_tdxlwings/__init__.py +193 -0
  2. tdrpa/_tdxlwings/__pycache__/__init__.cpython-311.pyc +0 -0
  3. tdrpa/_tdxlwings/__pycache__/__init__.cpython-38.pyc +0 -0
  4. tdrpa/_tdxlwings/__pycache__/_win32patch.cpython-311.pyc +0 -0
  5. tdrpa/_tdxlwings/__pycache__/_win32patch.cpython-38.pyc +0 -0
  6. tdrpa/_tdxlwings/__pycache__/_xlwindows.cpython-311.pyc +0 -0
  7. tdrpa/_tdxlwings/__pycache__/_xlwindows.cpython-38.pyc +0 -0
  8. tdrpa/_tdxlwings/__pycache__/apps.cpython-311.pyc +0 -0
  9. tdrpa/_tdxlwings/__pycache__/apps.cpython-38.pyc +0 -0
  10. tdrpa/_tdxlwings/__pycache__/base_classes.cpython-311.pyc +0 -0
  11. tdrpa/_tdxlwings/__pycache__/base_classes.cpython-38.pyc +0 -0
  12. tdrpa/_tdxlwings/__pycache__/com_server.cpython-311.pyc +0 -0
  13. tdrpa/_tdxlwings/__pycache__/com_server.cpython-38.pyc +0 -0
  14. tdrpa/_tdxlwings/__pycache__/constants.cpython-311.pyc +0 -0
  15. tdrpa/_tdxlwings/__pycache__/constants.cpython-38.pyc +0 -0
  16. tdrpa/_tdxlwings/__pycache__/expansion.cpython-311.pyc +0 -0
  17. tdrpa/_tdxlwings/__pycache__/expansion.cpython-38.pyc +0 -0
  18. tdrpa/_tdxlwings/__pycache__/main.cpython-311.pyc +0 -0
  19. tdrpa/_tdxlwings/__pycache__/main.cpython-38.pyc +0 -0
  20. tdrpa/_tdxlwings/__pycache__/udfs.cpython-311.pyc +0 -0
  21. tdrpa/_tdxlwings/__pycache__/udfs.cpython-38.pyc +0 -0
  22. tdrpa/_tdxlwings/__pycache__/utils.cpython-311.pyc +0 -0
  23. tdrpa/_tdxlwings/__pycache__/utils.cpython-38.pyc +0 -0
  24. tdrpa/_tdxlwings/_win32patch.py +90 -0
  25. tdrpa/_tdxlwings/_xlmac.py +2240 -0
  26. tdrpa/_tdxlwings/_xlwindows.py +2518 -0
  27. tdrpa/_tdxlwings/addin/Dictionary.cls +474 -0
  28. tdrpa/_tdxlwings/addin/IWebAuthenticator.cls +71 -0
  29. tdrpa/_tdxlwings/addin/WebClient.cls +772 -0
  30. tdrpa/_tdxlwings/addin/WebHelpers.bas +3203 -0
  31. tdrpa/_tdxlwings/addin/WebRequest.cls +875 -0
  32. tdrpa/_tdxlwings/addin/WebResponse.cls +453 -0
  33. tdrpa/_tdxlwings/addin/xlwings.xlam +0 -0
  34. tdrpa/_tdxlwings/apps.py +35 -0
  35. tdrpa/_tdxlwings/base_classes.py +1092 -0
  36. tdrpa/_tdxlwings/cli.py +1306 -0
  37. tdrpa/_tdxlwings/com_server.py +385 -0
  38. tdrpa/_tdxlwings/constants.py +3080 -0
  39. tdrpa/_tdxlwings/conversion/__init__.py +103 -0
  40. tdrpa/_tdxlwings/conversion/framework.py +147 -0
  41. tdrpa/_tdxlwings/conversion/numpy_conv.py +34 -0
  42. tdrpa/_tdxlwings/conversion/pandas_conv.py +184 -0
  43. tdrpa/_tdxlwings/conversion/standard.py +321 -0
  44. tdrpa/_tdxlwings/expansion.py +83 -0
  45. tdrpa/_tdxlwings/ext/__init__.py +3 -0
  46. tdrpa/_tdxlwings/ext/sql.py +73 -0
  47. tdrpa/_tdxlwings/html/xlwings-alert.html +71 -0
  48. tdrpa/_tdxlwings/js/xlwings.js +577 -0
  49. tdrpa/_tdxlwings/js/xlwings.ts +729 -0
  50. tdrpa/_tdxlwings/mac_dict.py +6399 -0
  51. tdrpa/_tdxlwings/main.py +5205 -0
  52. tdrpa/_tdxlwings/mistune/__init__.py +63 -0
  53. tdrpa/_tdxlwings/mistune/block_parser.py +366 -0
  54. tdrpa/_tdxlwings/mistune/inline_parser.py +216 -0
  55. tdrpa/_tdxlwings/mistune/markdown.py +84 -0
  56. tdrpa/_tdxlwings/mistune/renderers.py +220 -0
  57. tdrpa/_tdxlwings/mistune/scanner.py +121 -0
  58. tdrpa/_tdxlwings/mistune/util.py +41 -0
  59. tdrpa/_tdxlwings/pro/__init__.py +40 -0
  60. tdrpa/_tdxlwings/pro/_xlcalamine.py +536 -0
  61. tdrpa/_tdxlwings/pro/_xlofficejs.py +146 -0
  62. tdrpa/_tdxlwings/pro/_xlremote.py +1293 -0
  63. tdrpa/_tdxlwings/pro/custom_functions_code.js +150 -0
  64. tdrpa/_tdxlwings/pro/embedded_code.py +60 -0
  65. tdrpa/_tdxlwings/pro/udfs_officejs.py +549 -0
  66. tdrpa/_tdxlwings/pro/utils.py +199 -0
  67. tdrpa/_tdxlwings/quickstart.xlsm +0 -0
  68. tdrpa/_tdxlwings/quickstart_addin.xlam +0 -0
  69. tdrpa/_tdxlwings/quickstart_addin_ribbon.xlam +0 -0
  70. tdrpa/_tdxlwings/quickstart_fastapi/main.py +47 -0
  71. tdrpa/_tdxlwings/quickstart_fastapi/requirements.txt +3 -0
  72. tdrpa/_tdxlwings/quickstart_standalone.xlsm +0 -0
  73. tdrpa/_tdxlwings/reports.py +12 -0
  74. tdrpa/_tdxlwings/rest/__init__.py +1 -0
  75. tdrpa/_tdxlwings/rest/api.py +368 -0
  76. tdrpa/_tdxlwings/rest/serializers.py +103 -0
  77. tdrpa/_tdxlwings/server.py +14 -0
  78. tdrpa/_tdxlwings/udfs.py +775 -0
  79. tdrpa/_tdxlwings/utils.py +777 -0
  80. tdrpa/_tdxlwings/xlwings-0.31.6.applescript +30 -0
  81. tdrpa/_tdxlwings/xlwings.bas +2061 -0
  82. tdrpa/_tdxlwings/xlwings_custom_addin.bas +2042 -0
  83. tdrpa/_tdxlwings/xlwingslib.cp38-win_amd64.pyd +0 -0
  84. tdrpa/tdworker/__init__.pyi +12 -0
  85. tdrpa/tdworker/_clip.pyi +50 -0
  86. tdrpa/tdworker/_excel.pyi +743 -0
  87. tdrpa/tdworker/_file.pyi +77 -0
  88. tdrpa/tdworker/_img.pyi +226 -0
  89. tdrpa/tdworker/_network.pyi +94 -0
  90. tdrpa/tdworker/_os.pyi +47 -0
  91. tdrpa/tdworker/_sp.pyi +21 -0
  92. tdrpa/tdworker/_w.pyi +129 -0
  93. tdrpa/tdworker/_web.pyi +995 -0
  94. tdrpa/tdworker/_winE.pyi +228 -0
  95. tdrpa/tdworker/_winK.pyi +74 -0
  96. tdrpa/tdworker/_winM.pyi +117 -0
  97. tdrpa/tdworker.cp312-win_amd64.pyd +0 -0
  98. tdrpa_tdworker-1.2.13.2.dist-info/METADATA +38 -0
  99. tdrpa_tdworker-1.2.13.2.dist-info/RECORD +101 -0
  100. tdrpa_tdworker-1.2.13.2.dist-info/WHEEL +5 -0
  101. tdrpa_tdworker-1.2.13.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,2240 @@
1
+ import atexit
2
+ import datetime as dt
3
+ import os
4
+ import re
5
+ import shutil
6
+ import struct
7
+ import subprocess
8
+ from pathlib import Path
9
+
10
+ import aem
11
+ import appscript
12
+ import osax
13
+ import psutil
14
+ from appscript import its, k as kw, mactypes
15
+ from appscript.reference import CommandError
16
+
17
+ import tdrpa._tdxlwings as xlwings
18
+
19
+ from . import base_classes, mac_dict, utils
20
+ from .constants import ColorIndex
21
+ from .utils import (
22
+ VersionNumber,
23
+ col_name,
24
+ fullname_url_to_local_path,
25
+ int_to_rgb,
26
+ np_datetime_to_datetime,
27
+ read_config_sheet,
28
+ )
29
+
30
+ try:
31
+ import pandas as pd
32
+ except ImportError:
33
+ pd = None
34
+ try:
35
+ import numpy as np
36
+ except ImportError:
37
+ np = None
38
+ try:
39
+ from PIL import ImageGrab
40
+ except ImportError:
41
+ PIL = None
42
+
43
+
44
+ # Time types
45
+ time_types = (dt.date, dt.datetime)
46
+ if np:
47
+ time_types = time_types + (np.datetime64,)
48
+
49
+ cell_errors = (
50
+ "#DIV/0!",
51
+ "#N/A",
52
+ "#NAME?",
53
+ "#NULL!",
54
+ "#NUM!",
55
+ "#REF!",
56
+ "#VALUE!",
57
+ )
58
+
59
+
60
+ def _clean_value_data_element(
61
+ value, datetime_builder, empty_as, number_builder, err_to_str
62
+ ):
63
+ if value == "" or value == kw.missing_value:
64
+ return empty_as
65
+ if isinstance(value, dt.datetime) and datetime_builder is not dt.datetime:
66
+ value = datetime_builder(
67
+ month=value.month,
68
+ day=value.day,
69
+ year=value.year,
70
+ hour=value.hour,
71
+ minute=value.minute,
72
+ second=value.second,
73
+ microsecond=value.microsecond,
74
+ tzinfo=None,
75
+ )
76
+ elif number_builder is not None and isinstance(value, float):
77
+ value = number_builder(value)
78
+ return value
79
+
80
+
81
+ class Engine:
82
+ @property
83
+ def apps(self):
84
+ return Apps()
85
+
86
+ @property
87
+ def name(self):
88
+ return "excel"
89
+
90
+ @property
91
+ def type(self):
92
+ return "desktop"
93
+
94
+ @staticmethod
95
+ def prepare_xl_data_element(x, options):
96
+ if x is None:
97
+ return ""
98
+ elif pd and pd.isna(x):
99
+ return ""
100
+ elif np and isinstance(x, (np.floating, float)) and np.isnan(x):
101
+ return ""
102
+ elif np and isinstance(x, np.datetime64):
103
+ # handle numpy.datetime64
104
+ return np_datetime_to_datetime(x).replace(tzinfo=None)
105
+ elif np and isinstance(x, np.number):
106
+ return float(x)
107
+ elif pd and isinstance(x, pd.Timestamp):
108
+ # This transformation seems to be only needed on Python 2.6 (?)
109
+ return x.to_pydatetime().replace(tzinfo=None)
110
+ elif pd and isinstance(x, type(pd.NaT)):
111
+ return None
112
+ elif isinstance(x, dt.datetime):
113
+ # Make datetime timezone naive
114
+ return x.replace(tzinfo=None)
115
+ elif isinstance(x, bool):
116
+ # Must be tested before int!
117
+ return x
118
+ elif isinstance(x, int):
119
+ # appscript packs integers larger than SInt32 but smaller than SInt64 as
120
+ # typeSInt64, and integers larger than SInt64 as typeIEEE64BitFloatingPoint.
121
+ # Excel silently ignores typeSInt64. (GH 227)
122
+ return float(x)
123
+ return x
124
+
125
+ @staticmethod
126
+ def clean_value_data(data, datetime_builder, empty_as, number_builder, err_to_str):
127
+ return [
128
+ [
129
+ _clean_value_data_element(
130
+ c, datetime_builder, empty_as, number_builder, err_to_str
131
+ )
132
+ for c in row
133
+ ]
134
+ for row in data
135
+ ]
136
+
137
+
138
+ engine = Engine()
139
+
140
+
141
+ class Apps(base_classes.Apps):
142
+ def _iter_excel_instances(self):
143
+ asn = subprocess.check_output(
144
+ ["lsappinfo", "visibleprocesslist", "-includehidden"]
145
+ ).decode("utf-8")
146
+ for asn in asn.split(" "):
147
+ if "Microsoft_Excel" in asn:
148
+ pid_info = subprocess.check_output(
149
+ ["lsappinfo", "info", "-only", "pid", asn]
150
+ ).decode("utf-8")
151
+ if pid_info != '"pid"=[ NULL ] \n':
152
+ yield int(pid_info.split("=")[1])
153
+
154
+ def keys(self):
155
+ return list(self._iter_excel_instances())
156
+
157
+ def add(self, spec=None, add_book=None, visible=None):
158
+ return App(spec=spec, add_book=add_book, visible=visible)
159
+
160
+ def __iter__(self):
161
+ for pid in self._iter_excel_instances():
162
+ yield App(xl=pid)
163
+
164
+ def __len__(self):
165
+ return len(list(self._iter_excel_instances()))
166
+
167
+ def __getitem__(self, pid):
168
+ if pid not in self.keys():
169
+ raise KeyError("Could not find an Excel instance with this PID.")
170
+ return App(xl=pid)
171
+
172
+
173
+ class App(base_classes.App):
174
+ def __init__(self, spec=None, add_book=None, xl=None, visible=True):
175
+ if xl is None:
176
+ self._xl = appscript.app(
177
+ name=spec or "Microsoft Excel",
178
+ newinstance=True,
179
+ terms=mac_dict,
180
+ hide=not visible,
181
+ )
182
+ if visible:
183
+ self.activate() # Makes it behave like on Windows
184
+ elif isinstance(xl, int):
185
+ self._xl = appscript.app(pid=xl, terms=mac_dict)
186
+ else:
187
+ self._xl = xl
188
+
189
+ @property
190
+ def xl(self):
191
+ return self._xl
192
+
193
+ @xl.setter
194
+ def xl(self, value):
195
+ self._xl = value
196
+
197
+ @property
198
+ def api(self):
199
+ return self.xl
200
+
201
+ @property
202
+ def engine(self):
203
+ return engine
204
+
205
+ @property
206
+ def path(self):
207
+ return hfs_to_posix_path(self.xl.path.get())
208
+
209
+ @property
210
+ def pid(self):
211
+ data = (
212
+ self.xl.AS_appdata.target()
213
+ .addressdesc.coerce(aem.kae.typeKernelProcessID)
214
+ .data
215
+ )
216
+ (pid,) = struct.unpack("i", data)
217
+ return pid
218
+
219
+ @property
220
+ def version(self):
221
+ return self.xl.version.get()
222
+
223
+ @property
224
+ def selection(self):
225
+ sheet = self.books.active.sheets.active
226
+ try:
227
+ # fails if e.g. chart is selected
228
+ return Range(sheet, self.xl.selection.get_address())
229
+ except CommandError:
230
+ return None
231
+
232
+ def activate(self, steal_focus=False):
233
+ asn = subprocess.check_output(
234
+ ["lsappinfo", "visibleprocesslist", "-includehidden"]
235
+ ).decode("utf-8")
236
+ frontmost_asn = asn.split(" ")[0]
237
+ pid_info_frontmost = subprocess.check_output(
238
+ ["lsappinfo", "info", "-only", "pid", frontmost_asn]
239
+ ).decode("utf-8")
240
+ pid_frontmost = int(pid_info_frontmost.split("=")[1])
241
+ try:
242
+ appscript.app("System Events").processes[
243
+ its.unix_id == self.pid
244
+ ].frontmost.set(True)
245
+ if not steal_focus:
246
+ appscript.app("System Events").processes[
247
+ its.unix_id == pid_frontmost
248
+ ].frontmost.set(True)
249
+ except CommandError:
250
+ pass # may require root privileges (GH 1966)
251
+
252
+ @property
253
+ def visible(self):
254
+ try:
255
+ return (
256
+ appscript.app("System Events")
257
+ .processes[its.unix_id == self.pid]
258
+ .visible.get()[0]
259
+ )
260
+ except CommandError:
261
+ return None # may require root privileges (GH 1966)
262
+
263
+ @visible.setter
264
+ def visible(self, visible):
265
+ try:
266
+ appscript.app("System Events").processes[
267
+ its.unix_id == self.pid
268
+ ].visible.set(visible)
269
+ except CommandError:
270
+ pass # may require root privileges (GH 1966)
271
+
272
+ def quit(self):
273
+ self.xl.quit(saving=kw.no)
274
+
275
+ def kill(self):
276
+ psutil.Process(self.pid).kill()
277
+
278
+ @property
279
+ def screen_updating(self):
280
+ return self.xl.screen_updating.get()
281
+
282
+ @screen_updating.setter
283
+ def screen_updating(self, value):
284
+ self.xl.screen_updating.set(value)
285
+
286
+ @property
287
+ def display_alerts(self):
288
+ return self.xl.display_alerts.get()
289
+
290
+ @display_alerts.setter
291
+ def display_alerts(self, value):
292
+ self.xl.display_alerts.set(value)
293
+
294
+ @property
295
+ def enable_events(self):
296
+ return self.xl.enable_events.get()
297
+
298
+ @enable_events.setter
299
+ def enable_events(self, value):
300
+ self.xl.enable_events.set(value)
301
+
302
+ @property
303
+ def interactive(self):
304
+ # TODO: replace with specific error when Exceptions are refactored
305
+ raise xlwings.XlwingsError(
306
+ "Getting or setting 'app.interactive' isn't supported on macOS."
307
+ )
308
+
309
+ @interactive.setter
310
+ def interactive(self, value):
311
+ # TODO: replace with specific error when Exceptions are refactored
312
+ raise xlwings.XlwingsError(
313
+ "Getting or setting 'app.interactive' isn't supported on macOS."
314
+ )
315
+
316
+ @property
317
+ def startup_path(self):
318
+ return hfs_to_posix_path(self.xl.startup_path.get())
319
+
320
+ @property
321
+ def calculation(self):
322
+ return calculation_k2s[self.xl.calculation.get()]
323
+
324
+ @calculation.setter
325
+ def calculation(self, value):
326
+ self.xl.calculation.set(calculation_s2k[value])
327
+
328
+ def calculate(self):
329
+ self.xl.calculate()
330
+
331
+ @property
332
+ def books(self):
333
+ return Books(self)
334
+
335
+ @property
336
+ def hwnd(self):
337
+ return None
338
+
339
+ def run(self, macro, args):
340
+ kwargs = {"arg{0}".format(i): n for i, n in enumerate(args, 1)}
341
+ return self.xl.run_VB_macro(macro, **kwargs)
342
+
343
+ @property
344
+ def status_bar(self):
345
+ return self.xl.status_bar.get()
346
+
347
+ @status_bar.setter
348
+ def status_bar(self, value):
349
+ self.xl.status_bar.set(value)
350
+
351
+ @property
352
+ def cut_copy_mode(self):
353
+ modes = {kw.cut_mode: "cut", kw.copy_mode: "copy"}
354
+ return modes.get(self.xl.cut_copy_mode.get())
355
+
356
+ @cut_copy_mode.setter
357
+ def cut_copy_mode(self, value):
358
+ self.xl.cut_copy_mode.set(value)
359
+
360
+ def alert(self, prompt, title, buttons, mode, callback):
361
+ # OSAX (Open Scripting Architecture Extension) instance for StandardAdditions
362
+ # See /System/Library/ScriptingAdditions/StandardAdditions.osax
363
+ sa = osax.OSAX(pid=self.pid)
364
+ sa.activate() # Activate app so dialog box will be visible
365
+ modes = {
366
+ None: kw.informational,
367
+ "info": kw.informational,
368
+ "critical": kw.critical,
369
+ }
370
+ buttons_dict = {
371
+ None: "OK",
372
+ "ok": "OK",
373
+ "ok_cancel": ["Cancel", "OK"],
374
+ "yes_no": ["No", "Yes"],
375
+ "yes_no_cancel": ["Cancel", "No", "Yes"],
376
+ }
377
+ rv = sa.display_alert(
378
+ "" if title is None else title,
379
+ message="" if prompt is None else prompt,
380
+ buttons=buttons_dict[buttons],
381
+ as_=modes[mode],
382
+ )
383
+ return rv[kw.button_returned].lower()
384
+
385
+
386
+ class Books(base_classes.Books):
387
+ def __init__(self, app):
388
+ self.app = app
389
+
390
+ @property
391
+ def api(self):
392
+ return None
393
+
394
+ @property
395
+ def active(self):
396
+ return Book(self.app, self.app.xl.active_workbook.name.get())
397
+
398
+ def __call__(self, name_or_index):
399
+ b = Book(self.app, name_or_index)
400
+ if not b.xl.exists():
401
+ raise KeyError(name_or_index)
402
+ return b
403
+
404
+ def __contains__(self, key):
405
+ return Book(self.app, key).xl.exists()
406
+
407
+ def __len__(self):
408
+ return self.app.xl.count(each=kw.workbook)
409
+
410
+ def add(self):
411
+ if self.app.visible:
412
+ self.app.activate()
413
+ xl = self.app.xl.make(new=kw.workbook)
414
+ wb = Book(self.app, xl.name.get())
415
+ return wb
416
+
417
+ def open(
418
+ self,
419
+ fullname,
420
+ update_links=None,
421
+ read_only=None,
422
+ format=None,
423
+ password=None,
424
+ write_res_password=None,
425
+ ignore_read_only_recommended=None,
426
+ origin=None,
427
+ delimiter=None,
428
+ editable=None,
429
+ notify=None,
430
+ converter=None,
431
+ add_to_mru=None,
432
+ local=None,
433
+ corrupt_load=None,
434
+ ):
435
+ # TODO: format and origin currently require a native appscript keyword,
436
+ # read_only doesn't seem to work
437
+ # Unsupported params
438
+ if local is not None:
439
+ # TODO: replace with specific error when Exceptions are refactored
440
+ raise xlwings.XlwingsError("local is not supported on macOS")
441
+ if corrupt_load is not None:
442
+ # TODO: replace with specific error when Exceptions are refactored
443
+ raise xlwings.XlwingsError("corrupt_load is not supported on macOS")
444
+ # update_links: on Windows only constants 0 and 3 seem to be supported in
445
+ # this context
446
+ if update_links:
447
+ update_links = kw.update_remote_and_external_links
448
+ else:
449
+ update_links = kw.do_not_update_links
450
+ if self.app.visible:
451
+ self.app.activate()
452
+ filename = os.path.basename(fullname)
453
+ self.app.xl.open_workbook(
454
+ workbook_file_name=fullname,
455
+ update_links=update_links,
456
+ read_only=read_only,
457
+ format=format,
458
+ password=password,
459
+ write_reserved_password=write_res_password,
460
+ ignore_read_only_recommended=ignore_read_only_recommended,
461
+ origin=origin,
462
+ delimiter=delimiter,
463
+ editable=editable,
464
+ notify=notify,
465
+ converter=converter,
466
+ add_to_mru=add_to_mru,
467
+ timeout=-1,
468
+ )
469
+ wb = Book(self.app, filename)
470
+ return wb
471
+
472
+ def __iter__(self):
473
+ n = len(self)
474
+ for i in range(n):
475
+ yield Book(self.app, i + 1)
476
+
477
+
478
+ class Book(base_classes.Book):
479
+ def __init__(self, app, name_or_index):
480
+ self._app = app
481
+ self.xl = app.xl.workbooks[name_or_index]
482
+
483
+ @property
484
+ def app(self):
485
+ return self._app
486
+
487
+ @property
488
+ def api(self):
489
+ return self.xl
490
+
491
+ def json(self):
492
+ raise NotImplementedError()
493
+
494
+ @property
495
+ def name(self):
496
+ return self.xl.name.get()
497
+
498
+ @property
499
+ def sheets(self):
500
+ return Sheets(self)
501
+
502
+ def close(self):
503
+ self.xl.close(saving=kw.no)
504
+
505
+ def save(self, path, password):
506
+ saved_path = self.xl.properties().get(kw.path)
507
+ source_ext = os.path.splitext(self.name)[1] if saved_path else None
508
+ target_ext = os.path.splitext(path)[1] if path else ".xlsx"
509
+ if saved_path and source_ext == target_ext:
510
+ file_format = self.xl.properties().get(kw.file_format)
511
+ else:
512
+ ext_to_file_format = {
513
+ ".xlsx": kw.Excel_XML_file_format,
514
+ ".xlsm": kw.macro_enabled_XML_file_format,
515
+ ".xlsb": kw.Excel_binary_file_format,
516
+ ".xltm": kw.macro_enabled_template_file_format,
517
+ ".xltx": kw.template_file_format,
518
+ ".xlam": kw.add_in_file_format,
519
+ ".xls": kw.Excel98to2004_file_format,
520
+ ".xlt": kw.Excel98to2004_template_file_format,
521
+ ".xla": kw.Excel98to2004_add_in_file_format,
522
+ }
523
+
524
+ file_format = ext_to_file_format[target_ext]
525
+ if (saved_path != "") and (path is None):
526
+ # Previously saved: Save under existing name
527
+ self.xl.save(timeout=-1)
528
+ elif (
529
+ (saved_path != "") and (path is not None) and (os.path.split(path)[0] == "")
530
+ ):
531
+ # Save existing book under new name in cwd if no path has been provided
532
+ save_as_name = path
533
+ path = os.path.join(os.getcwd(), path)
534
+ hfs_path = posix_to_hfs_path(os.path.realpath(path))
535
+ self.xl.save_workbook_as(
536
+ filename=hfs_path,
537
+ overwrite=True,
538
+ file_format=file_format,
539
+ timeout=-1,
540
+ password=password,
541
+ )
542
+ self.xl = self.app.xl.workbooks[save_as_name]
543
+ elif (saved_path == "") and (path is None):
544
+ # Previously unsaved: Save under current name in current working directory
545
+ save_as_name = self.xl.name.get() + ".xlsx"
546
+ path = os.path.join(os.getcwd(), save_as_name)
547
+ hfs_path = posix_to_hfs_path(os.path.realpath(path))
548
+ self.xl.save_workbook_as(
549
+ filename=hfs_path,
550
+ overwrite=True,
551
+ file_format=file_format,
552
+ timeout=-1,
553
+ password=password,
554
+ )
555
+ self.xl = self.app.xl.workbooks[save_as_name]
556
+ elif path:
557
+ # Save under new name/location
558
+ hfs_path = posix_to_hfs_path(os.path.realpath(path))
559
+ self.xl.save_workbook_as(
560
+ filename=hfs_path,
561
+ overwrite=True,
562
+ file_format=file_format,
563
+ timeout=-1,
564
+ password=password,
565
+ )
566
+ self.xl = self.app.xl.workbooks[os.path.basename(path)]
567
+
568
+ @property
569
+ def fullname(self):
570
+ display_alerts = self.app.display_alerts
571
+ self.app.display_alerts = False
572
+ # This causes a pop-up if there's a pw protected sheet, see #1377
573
+ path = self.xl.properties().get(kw.full_name)
574
+ if "://" in path:
575
+ config = read_config_sheet(xlwings.Book(impl=self))
576
+ self.app.display_alerts = display_alerts
577
+ return fullname_url_to_local_path(
578
+ url=path,
579
+ sheet_onedrive_consumer_config=config.get("ONEDRIVE_CONSUMER_MAC"),
580
+ sheet_onedrive_commercial_config=config.get("ONEDRIVE_COMMERCIAL_MAC"),
581
+ sheet_sharepoint_config=config.get("SHAREPOINT_MAC"),
582
+ )
583
+ else:
584
+ self.app.display_alerts = display_alerts
585
+ return path
586
+
587
+ @property
588
+ def names(self):
589
+ return Names(parent=self, xl=self.xl.named_items)
590
+
591
+ def activate(self):
592
+ self.xl.activate_object()
593
+
594
+ def to_pdf(self, path, quality=None):
595
+ # quality parameter for compatibility
596
+ hfs_path = posix_to_hfs_path(path)
597
+ display_alerts = self.app.display_alerts
598
+ self.app.display_alerts = False
599
+ if Path(path).exists():
600
+ # Errors out with Parameter error (OSERROR: -50) otherwise
601
+ os.unlink(path)
602
+ self.xl.save(in_=hfs_path, as_=kw.PDF_file_format)
603
+ self.app.display_alerts = display_alerts
604
+
605
+
606
+ class Sheets(base_classes.Sheets):
607
+ def __init__(self, workbook):
608
+ self.workbook = workbook
609
+
610
+ @property
611
+ def api(self):
612
+ return None
613
+
614
+ @property
615
+ def active(self):
616
+ return Sheet(self.workbook, self.workbook.xl.active_sheet.name.get())
617
+
618
+ def __call__(self, name_or_index):
619
+ return Sheet(self.workbook, name_or_index)
620
+
621
+ def __len__(self):
622
+ return self.workbook.xl.count(each=kw.worksheet)
623
+
624
+ def __iter__(self):
625
+ for i in range(len(self)):
626
+ yield self(i + 1)
627
+
628
+ def add(self, before=None, after=None, name=None):
629
+ if before is None and after is None:
630
+ before = self.workbook.app.books.active.sheets.active
631
+ if before:
632
+ position = before.xl.before
633
+ else:
634
+ position = after.xl.after
635
+ xl = self.workbook.xl.make(new=kw.worksheet, at=position)
636
+ if name is not None:
637
+ xl.name.set(name)
638
+ xl = self.workbook.xl.worksheets[name]
639
+ return Sheet(self.workbook, xl.name.get())
640
+
641
+
642
+ class Sheet(base_classes.Sheet):
643
+ def __init__(self, workbook, name_or_index):
644
+ self.workbook = workbook
645
+ self.xl = workbook.xl.worksheets[name_or_index]
646
+
647
+ @property
648
+ def api(self):
649
+ return self.xl
650
+
651
+ @property
652
+ def name(self):
653
+ return self.xl.name.get()
654
+
655
+ @name.setter
656
+ def name(self, value):
657
+ self.xl.name.set(value)
658
+ self.xl = self.workbook.xl.worksheets[value]
659
+
660
+ @property
661
+ def names(self):
662
+ return Names(parent=self, xl=self.xl.named_items)
663
+
664
+ @property
665
+ def book(self):
666
+ return self.workbook
667
+
668
+ @property
669
+ def index(self):
670
+ return self.xl.entry_index.get()
671
+
672
+ def range(self, arg1, arg2=None):
673
+ if isinstance(arg1, tuple):
674
+ if len(arg1) == 2:
675
+ if 0 in arg1:
676
+ raise IndexError(
677
+ "Attempted to access 0-based Range. "
678
+ "xlwings/Excel Ranges are 1-based."
679
+ )
680
+ row1 = arg1[0]
681
+ col1 = arg1[1]
682
+ address1 = self.xl.rows[row1].columns[col1].get_address()
683
+ elif len(arg1) == 4:
684
+ return Range(self, arg1)
685
+ else:
686
+ raise ValueError("Invalid parameters")
687
+ elif isinstance(arg1, Range):
688
+ row1 = min(arg1.row, arg2.row)
689
+ col1 = min(arg1.column, arg2.column)
690
+ address1 = self.xl.rows[row1].columns[col1].get_address()
691
+ elif isinstance(arg1, str):
692
+ address1 = arg1.split(":")[0]
693
+ else:
694
+ raise ValueError("Invalid parameters")
695
+
696
+ if isinstance(arg2, tuple):
697
+ if 0 in arg2:
698
+ raise IndexError(
699
+ "Attempted to access 0-based Range. "
700
+ "xlwings/Excel Ranges are 1-based."
701
+ )
702
+ row2 = arg2[0]
703
+ col2 = arg2[1]
704
+ address2 = self.xl.rows[row2].columns[col2].get_address()
705
+ elif isinstance(arg2, Range):
706
+ row2 = max(arg1.row + arg1.shape[0] - 1, arg2.row + arg2.shape[0] - 1)
707
+ col2 = max(arg1.column + arg1.shape[1] - 1, arg2.column + arg2.shape[1] - 1)
708
+ address2 = self.xl.rows[row2].columns[col2].get_address()
709
+ elif isinstance(arg2, str):
710
+ address2 = arg2
711
+ elif arg2 is None:
712
+ if isinstance(arg1, str) and len(arg1.split(":")) == 2:
713
+ address2 = arg1.split(":")[1]
714
+ else:
715
+ return Range(self, "{0}".format(address1))
716
+ else:
717
+ raise ValueError("Invalid parameters")
718
+
719
+ return Range(self, "{0}:{1}".format(address1, address2))
720
+
721
+ @property
722
+ def cells(self):
723
+ return self.range(
724
+ (1, 1), (self.xl.count(each=kw.row), self.xl.count(each=kw.column))
725
+ )
726
+
727
+ def activate(self):
728
+ self.xl.activate_object()
729
+
730
+ def select(self):
731
+ self.xl.select()
732
+
733
+ def clear_contents(self):
734
+ self.xl.used_range.clear_contents()
735
+
736
+ def clear_formats(self):
737
+ self.xl.used_range.clear_formats()
738
+
739
+ def clear(self):
740
+ self.xl.used_range.clear_range()
741
+
742
+ def autofit(self, axis=None):
743
+ num_columns = self.xl.count(each=kw.column)
744
+ num_rows = self.xl.count(each=kw.row)
745
+ address = self.range((1, 1), (num_rows, num_columns)).address
746
+ alerts_state = self.book.app.screen_updating
747
+ self.book.app.screen_updating = False
748
+ if axis == "rows" or axis == "r":
749
+ self.xl.rows[address].autofit()
750
+ elif axis == "columns" or axis == "c":
751
+ self.xl.columns[address].autofit()
752
+ elif axis is None:
753
+ self.xl.rows[address].autofit()
754
+ self.xl.columns[address].autofit()
755
+ self.book.app.screen_updating = alerts_state
756
+
757
+ def delete(self):
758
+ alerts_state = self.book.app.xl.display_alerts.get()
759
+ self.book.app.xl.display_alerts.set(False)
760
+ self.xl.delete()
761
+ self.book.app.xl.display_alerts.set(alerts_state)
762
+
763
+ def copy(self, before, after):
764
+ if before:
765
+ before = before.xl
766
+ if after:
767
+ after = after.xl
768
+ self.xl.copy_worksheet(before_=before, after_=after)
769
+
770
+ @property
771
+ def charts(self):
772
+ return Charts(self)
773
+
774
+ @property
775
+ def shapes(self):
776
+ return Shapes(self)
777
+
778
+ @property
779
+ def tables(self):
780
+ return Tables(self)
781
+
782
+ @property
783
+ def pictures(self):
784
+ return Pictures(self)
785
+
786
+ @property
787
+ def used_range(self):
788
+ return Range(self, self.xl.used_range.get_address())
789
+
790
+ @property
791
+ def visible(self):
792
+ return True if self.xl.visible.get() == kw.sheet_visible else False
793
+
794
+ @visible.setter
795
+ def visible(self, value):
796
+ self.xl.visible.set(value)
797
+
798
+ @property
799
+ def page_setup(self):
800
+ return PageSetup(self, self.xl.page_setup_object)
801
+
802
+
803
+ class Range(base_classes.Range):
804
+ def __init__(self, sheet, address):
805
+ self.sheet = sheet
806
+ self.options = None # Assigned by main.Range to keep API of sheet.range clean
807
+ if isinstance(address, tuple):
808
+ self._coords = address
809
+ row, col, nrows, ncols = address
810
+ if nrows and ncols:
811
+ self.xl = sheet.xl.cells[
812
+ "%s:%s"
813
+ % (
814
+ sheet.xl.rows[row].columns[col].get_address(),
815
+ sheet.xl.rows[row + nrows - 1]
816
+ .columns[col + ncols - 1]
817
+ .get_address(),
818
+ )
819
+ ]
820
+ else:
821
+ self.xl = None
822
+ else:
823
+ self.xl = sheet.xl.cells[address]
824
+ self._coords = None
825
+
826
+ @property
827
+ def coords(self):
828
+ if self._coords is None:
829
+ self._coords = (
830
+ self.xl.first_row_index.get(),
831
+ self.xl.first_column_index.get(),
832
+ self.xl.count(each=kw.row),
833
+ self.xl.count(each=kw.column),
834
+ )
835
+ return self._coords
836
+
837
+ @property
838
+ def api(self):
839
+ return self.xl
840
+
841
+ def __len__(self):
842
+ return self.coords[2] * self.coords[3]
843
+
844
+ @property
845
+ def row(self):
846
+ return self.coords[0]
847
+
848
+ @property
849
+ def column(self):
850
+ return self.coords[1]
851
+
852
+ @property
853
+ def shape(self):
854
+ return self.coords[2], self.coords[3]
855
+
856
+ @property
857
+ def raw_value(self):
858
+ def ensure_2d(values):
859
+ # Usually done in converter, but macOS doesn't deliver any info about
860
+ # errors with values
861
+ if not isinstance(values, list):
862
+ return [[values]]
863
+ elif not isinstance(values[0], list):
864
+ return [values]
865
+
866
+ if self.xl is not None:
867
+ values = self.xl.value.get()
868
+ if self.options.get("err_to_str", False):
869
+ string_values = self.xl.string_value.get()
870
+ values = ensure_2d(values)
871
+ string_values = ensure_2d(string_values)
872
+ for row_ix, row in enumerate(string_values):
873
+ for col_ix, c in enumerate(row):
874
+ if c in cell_errors:
875
+ values[row_ix][col_ix] = c
876
+ return values
877
+
878
+ @raw_value.setter
879
+ def raw_value(self, value):
880
+ if self.xl is not None:
881
+ self.xl.value.set(value)
882
+
883
+ def clear_contents(self):
884
+ if self.xl is not None:
885
+ alerts_state = self.sheet.book.app.screen_updating
886
+ self.sheet.book.app.screen_updating = False
887
+ self.xl.clear_contents()
888
+ self.sheet.book.app.screen_updating = alerts_state
889
+
890
+ def clear_formats(self):
891
+ if self.xl is not None:
892
+ alerts_state = self.sheet.book.app.screen_updating
893
+ self.sheet.book.app.screen_updating = False
894
+ self.xl.clear_formats()
895
+ self.sheet.book.app.screen_updating = alerts_state
896
+
897
+ def clear(self):
898
+ if self.xl is not None:
899
+ alerts_state = self.sheet.book.app.screen_updating
900
+ self.sheet.book.app.screen_updating = False
901
+ self.xl.clear_range()
902
+ self.sheet.book.app.screen_updating = alerts_state
903
+
904
+ def end(self, direction):
905
+ direction = directions_s2k.get(direction, direction)
906
+ return Range(self.sheet, self.xl.get_end(direction=direction).get_address())
907
+
908
+ @property
909
+ def formula(self):
910
+ if self.xl is not None:
911
+ return self.xl.formula.get()
912
+
913
+ @formula.setter
914
+ def formula(self, value):
915
+ if self.xl is not None:
916
+ self.xl.formula.set(value)
917
+
918
+ @property
919
+ def formula2(self):
920
+ if self.xl is not None:
921
+ return self.xl.formula2.get()
922
+
923
+ @formula2.setter
924
+ def formula2(self, value):
925
+ if self.xl is not None:
926
+ self.xl.formula2.set(value)
927
+
928
+ @property
929
+ def formula_array(self):
930
+ if self.xl is not None:
931
+ rv = self.xl.formula_array.get()
932
+ return None if rv == kw.missing_value else rv
933
+
934
+ @formula_array.setter
935
+ def formula_array(self, value):
936
+ if self.xl is not None:
937
+ self.xl.formula_array.set(value)
938
+
939
+ @property
940
+ def font(self):
941
+ return Font(self, self.xl.font_object)
942
+
943
+ @property
944
+ def column_width(self):
945
+ if self.xl is not None:
946
+ rv = self.xl.column_width.get()
947
+ return None if rv == kw.missing_value else rv
948
+ else:
949
+ return 0
950
+
951
+ @column_width.setter
952
+ def column_width(self, value):
953
+ if self.xl is not None:
954
+ self.xl.column_width.set(value)
955
+
956
+ @property
957
+ def row_height(self):
958
+ if self.xl is not None:
959
+ rv = self.xl.row_height.get()
960
+ return None if rv == kw.missing_value else rv
961
+ else:
962
+ return 0
963
+
964
+ @row_height.setter
965
+ def row_height(self, value):
966
+ if self.xl is not None:
967
+ self.xl.row_height.set(value)
968
+
969
+ @property
970
+ def width(self):
971
+ if self.xl is not None:
972
+ return self.xl.width.get()
973
+ else:
974
+ return 0
975
+
976
+ @property
977
+ def height(self):
978
+ if self.xl is not None:
979
+ return self.xl.height.get()
980
+ else:
981
+ return 0
982
+
983
+ @property
984
+ def left(self):
985
+ return self.xl.properties().get(kw.left_position)
986
+
987
+ @property
988
+ def top(self):
989
+ return self.xl.properties().get(kw.top)
990
+
991
+ @property
992
+ def has_array(self):
993
+ if self.xl is not None:
994
+ return self.xl.has_array.get()
995
+
996
+ @property
997
+ def number_format(self):
998
+ if self.xl is not None:
999
+ rv = self.xl.number_format.get()
1000
+ return None if rv == kw.missing_value else rv
1001
+
1002
+ @number_format.setter
1003
+ def number_format(self, value):
1004
+ if self.xl is not None:
1005
+ alerts_state = self.sheet.book.app.screen_updating
1006
+ self.sheet.book.app.screen_updating = False
1007
+ self.xl.number_format.set(value)
1008
+ self.sheet.book.app.screen_updating = alerts_state
1009
+
1010
+ def get_address(self, row_absolute, col_absolute, external):
1011
+ if self.xl is not None:
1012
+ return self.xl.get_address(
1013
+ row_absolute=row_absolute,
1014
+ column_absolute=col_absolute,
1015
+ external=external,
1016
+ )
1017
+
1018
+ @property
1019
+ def address(self):
1020
+ if self.xl is not None:
1021
+ return self.xl.get_address()
1022
+ else:
1023
+ row, col, nrows, ncols = self.coords
1024
+ return "$%s$%s{%sx%s}" % (col_name(col), row, nrows, ncols)
1025
+
1026
+ @property
1027
+ def current_region(self):
1028
+ return Range(self.sheet, self.xl.current_region.get_address())
1029
+
1030
+ def autofit(self, axis=None):
1031
+ if self.xl is not None:
1032
+ address = self.address
1033
+ alerts_state = self.sheet.book.app.screen_updating
1034
+ self.sheet.book.app.screen_updating = False
1035
+ if axis == "rows" or axis == "r":
1036
+ self.sheet.xl.rows[address].autofit()
1037
+ elif axis == "columns" or axis == "c":
1038
+ self.sheet.xl.columns[address].autofit()
1039
+ elif axis is None:
1040
+ self.sheet.xl.rows[address].autofit()
1041
+ self.sheet.xl.columns[address].autofit()
1042
+ self.sheet.book.app.screen_updating = alerts_state
1043
+
1044
+ def insert(self, shift=None, copy_origin=None):
1045
+ # copy_origin is not supported on mac
1046
+ shifts = {"down": kw.shift_down, "right": kw.shift_to_right, None: None}
1047
+ self.xl.insert_into_range(shift=shifts[shift])
1048
+
1049
+ def delete(self, shift=None):
1050
+ shifts = {"up": kw.shift_up, "left": kw.shift_to_left, None: None}
1051
+ self.xl.delete_range(shift=shifts[shift])
1052
+
1053
+ def copy(self, destination=None):
1054
+ self.xl.copy_range(destination=destination.api if destination else None)
1055
+
1056
+ def paste(self, paste=None, operation=None, skip_blanks=False, transpose=False):
1057
+ pastes = {
1058
+ # all_merging_conditional_formats unsupported on mac
1059
+ "all": kw.paste_all,
1060
+ "all_except_borders": kw.paste_all_except_borders,
1061
+ "all_using_source_theme": kw.paste_all_using_source_theme,
1062
+ "column_widths": kw.paste_column_widths,
1063
+ "comments": kw.paste_comments,
1064
+ "formats": kw.paste_formats,
1065
+ "formulas": kw.paste_formulas,
1066
+ "formulas_and_number_formats": kw.paste_formulas_and_number_formats,
1067
+ "validation": kw.paste_validation,
1068
+ "values": kw.paste_values,
1069
+ "values_and_number_formats": kw.paste_values_and_number_formats,
1070
+ None: None,
1071
+ }
1072
+
1073
+ operations = {
1074
+ "add": kw.paste_special_operation_add,
1075
+ "divide": kw.paste_special_operation_divide,
1076
+ "multiply": kw.paste_special_operation_multiply,
1077
+ "subtract": kw.paste_special_operation_subtract,
1078
+ None: None,
1079
+ }
1080
+
1081
+ self.xl.paste_special(
1082
+ what=pastes[paste],
1083
+ operation=operations[operation],
1084
+ skip_blanks=skip_blanks,
1085
+ transpose=transpose,
1086
+ )
1087
+
1088
+ @property
1089
+ def hyperlink(self):
1090
+ try:
1091
+ return self.xl.hyperlinks[1].address.get()
1092
+ except CommandError:
1093
+ raise Exception("The cell doesn't seem to contain a hyperlink!")
1094
+
1095
+ def add_hyperlink(self, address, text_to_display=None, screen_tip=None):
1096
+ if self.xl is not None:
1097
+ self.xl.make(
1098
+ at=self.xl,
1099
+ new=kw.hyperlink,
1100
+ with_properties={
1101
+ kw.address: address,
1102
+ kw.text_to_display: text_to_display,
1103
+ kw.screen_tip: screen_tip,
1104
+ },
1105
+ )
1106
+
1107
+ @property
1108
+ def color(self):
1109
+ if (
1110
+ not self.xl
1111
+ or self.xl.interior_object.color_index.get() == kw.color_index_none
1112
+ ):
1113
+ return None
1114
+ else:
1115
+ return tuple(self.xl.interior_object.color.get())
1116
+
1117
+ @color.setter
1118
+ def color(self, color_or_rgb):
1119
+ if isinstance(color_or_rgb, str):
1120
+ color_or_rgb = utils.hex_to_rgb(color_or_rgb)
1121
+ if self.xl is not None:
1122
+ if color_or_rgb is None:
1123
+ self.xl.interior_object.color_index.set(ColorIndex.xlColorIndexNone)
1124
+ elif isinstance(color_or_rgb, int):
1125
+ self.xl.interior_object.color.set(int_to_rgb(color_or_rgb))
1126
+ else:
1127
+ self.xl.interior_object.color.set(color_or_rgb)
1128
+
1129
+ @property
1130
+ def name(self):
1131
+ if not self.xl:
1132
+ return None
1133
+ xl = self.xl.named_item
1134
+ if xl.get() == kw.missing_value:
1135
+ return None
1136
+ else:
1137
+ return Name(self.sheet.book, xl=xl)
1138
+
1139
+ @name.setter
1140
+ def name(self, value):
1141
+ if self.xl is not None:
1142
+ self.xl.name.set(value)
1143
+
1144
+ def __call__(self, arg1, arg2=None):
1145
+ if arg2 is None:
1146
+ col = (arg1 - 1) % self.shape[1]
1147
+ row = int((arg1 - 1 - col) / self.shape[1])
1148
+ return self(1 + row, 1 + col)
1149
+ else:
1150
+ return Range(
1151
+ self.sheet,
1152
+ self.sheet.xl.rows[self.row + arg1 - 1]
1153
+ .columns[self.column + arg2 - 1]
1154
+ .get_address(),
1155
+ )
1156
+
1157
+ @property
1158
+ def rows(self):
1159
+ row = self.row
1160
+ col1 = self.column
1161
+ col2 = col1 + self.shape[1] - 1
1162
+ return [
1163
+ self.sheet.range((row + i, col1), (row + i, col2))
1164
+ for i in range(self.shape[0])
1165
+ ]
1166
+
1167
+ @property
1168
+ def columns(self):
1169
+ col = self.column
1170
+ row1 = self.row
1171
+ row2 = row1 + self.shape[0] - 1
1172
+ sht = self.sheet
1173
+ return [
1174
+ sht.range((row1, col + i), (row2, col + i)) for i in range(self.shape[1])
1175
+ ]
1176
+
1177
+ def select(self):
1178
+ if self.xl is not None:
1179
+ return self.xl.select()
1180
+
1181
+ @property
1182
+ def merge_area(self):
1183
+ return Range(self.sheet, self.xl.merge_area.get_address())
1184
+
1185
+ @property
1186
+ def merge_cells(self):
1187
+ return self.xl.merge_cells.get()
1188
+
1189
+ def merge(self, across):
1190
+ self.xl.merge(across=across)
1191
+
1192
+ def unmerge(self):
1193
+ self.xl.unmerge()
1194
+
1195
+ @property
1196
+ def table(self):
1197
+ if self.xl.list_object.name.get() == kw.missing_value:
1198
+ return None
1199
+ else:
1200
+ return Table(self.sheet, self.xl.list_object.name.get())
1201
+
1202
+ @property
1203
+ def characters(self):
1204
+ # This is broken with AppleScript/Excel 2016
1205
+ return Characters(parent=self, xl=self.xl.characters)
1206
+
1207
+ @property
1208
+ def wrap_text(self):
1209
+ return self.xl.wrap_text.get()
1210
+
1211
+ @wrap_text.setter
1212
+ def wrap_text(self, value):
1213
+ self.xl.wrap_text.set(value)
1214
+
1215
+ @property
1216
+ def note(self):
1217
+ try:
1218
+ # No easy way to check whether there's a comment like on Windows
1219
+ return (
1220
+ Note(parent=self, xl=self.xl.Excel_comment)
1221
+ if self.xl.Excel_comment.Excel_comment_text()
1222
+ else None
1223
+ )
1224
+ except appscript.reference.CommandError:
1225
+ return None
1226
+
1227
+ def copy_picture(self, appearance, format):
1228
+ _appearance = {"screen": kw.screen, "printer": kw.printer}
1229
+ _format = {"picture": kw.picture, "bitmap": kw.bitmap}
1230
+ self.xl.copy_picture(appearance=_appearance[appearance], format=_format[format])
1231
+
1232
+ def to_png(self, path):
1233
+ self.copy_picture(appearance="screen", format="bitmap")
1234
+ im = ImageGrab.grabclipboard()
1235
+ im.save(path)
1236
+
1237
+ def to_pdf(self, path, quality=None):
1238
+ raise xlwings.XlwingsError("Range.to_pdf() isn't supported on macOS.")
1239
+
1240
+ def autofill(self, destination, type_):
1241
+ types = {
1242
+ "fill_copy": kw.fill_copy,
1243
+ "fill_days": kw.fill_days,
1244
+ "fill_default": kw.fill_default,
1245
+ "fill_formats": kw.fill_formats,
1246
+ "fill_months": kw.fill_months,
1247
+ "fill_series": kw.fill_series,
1248
+ "fill_values": kw.fill_values,
1249
+ "fill_weekdays": kw.fill_weekdays,
1250
+ "fill_years": kw.fill_years,
1251
+ "growth_trend": kw.growth_trend,
1252
+ "linear_trend": kw.linear_trend,
1253
+ "flash_fill": kw.flashfill,
1254
+ }
1255
+ self.xl.autofill(destination=destination.api, type=types[type_])
1256
+
1257
+
1258
+ class Shape(base_classes.Shape):
1259
+ def __init__(self, parent, key):
1260
+ self._parent = parent
1261
+ self.xl = parent.xl.shapes[key]
1262
+
1263
+ @property
1264
+ def parent(self):
1265
+ return self._parent
1266
+
1267
+ @property
1268
+ def api(self):
1269
+ return self.xl
1270
+
1271
+ @property
1272
+ def name(self):
1273
+ return self.xl.name.get()
1274
+
1275
+ @name.setter
1276
+ def name(self, value):
1277
+ self.xl.name.set(value)
1278
+
1279
+ @property
1280
+ def type(self):
1281
+ return shape_types_k2s[self.xl.shape_type.get()]
1282
+
1283
+ @property
1284
+ def left(self):
1285
+ return self.xl.left_position.get()
1286
+
1287
+ @left.setter
1288
+ def left(self, value):
1289
+ self.xl.left_position.set(value)
1290
+
1291
+ @property
1292
+ def top(self):
1293
+ return self.xl.top.get()
1294
+
1295
+ @top.setter
1296
+ def top(self, value):
1297
+ self.xl.top.set(value)
1298
+
1299
+ @property
1300
+ def width(self):
1301
+ return self.xl.width.get()
1302
+
1303
+ @width.setter
1304
+ def width(self, value):
1305
+ self.xl.width.set(value)
1306
+
1307
+ @property
1308
+ def height(self):
1309
+ return self.xl.height.get()
1310
+
1311
+ @height.setter
1312
+ def height(self, value):
1313
+ self.xl.height.set(value)
1314
+
1315
+ def delete(self):
1316
+ self.xl.delete()
1317
+
1318
+ @property
1319
+ def index(self):
1320
+ return self.xl.entry_index.get()
1321
+
1322
+ def activate(self):
1323
+ # self.xl.activate_object() # doesn't work?
1324
+ self.xl.select()
1325
+
1326
+ def scale_height(self, factor, relative_to_original_size, scale):
1327
+ self.xl.scale_height(
1328
+ scale=scaling[scale],
1329
+ relative_to_original_size=relative_to_original_size,
1330
+ factor=factor,
1331
+ )
1332
+
1333
+ def scale_width(self, factor, relative_to_original_size, scale):
1334
+ self.xl.scale_width(
1335
+ scale=scaling[scale],
1336
+ relative_to_original_size=relative_to_original_size,
1337
+ factor=factor,
1338
+ )
1339
+
1340
+ @property
1341
+ def text(self):
1342
+ if self.xl.shape_text_frame.has_text.get():
1343
+ return self.xl.shape_text_frame.text_range.content.get()
1344
+
1345
+ @text.setter
1346
+ def text(self, value):
1347
+ self.xl.shape_text_frame.text_range.content.set(value)
1348
+
1349
+ @property
1350
+ def font(self):
1351
+ return Font(self, self.xl.shape_text_frame.text_range.font)
1352
+
1353
+ @property
1354
+ def characters(self):
1355
+ raise AttributeError("Characters isn't supported on macOS with shapes.")
1356
+
1357
+
1358
+ class Font(base_classes.Font):
1359
+ def __init__(self, parent, xl):
1360
+ # xl can be font or font_object
1361
+ self.parent = parent
1362
+ self.xl = xl
1363
+
1364
+ @property
1365
+ def api(self):
1366
+ return self.xl
1367
+
1368
+ @property
1369
+ def bold(self):
1370
+ return self.xl.bold.get()
1371
+
1372
+ @bold.setter
1373
+ def bold(self, value):
1374
+ self.xl.bold.set(value)
1375
+
1376
+ @property
1377
+ def italic(self):
1378
+ return self.xl.italic.get()
1379
+
1380
+ @italic.setter
1381
+ def italic(self, value):
1382
+ self.xl.italic.set(value)
1383
+
1384
+ @property
1385
+ def size(self):
1386
+ return self.xl.font_size.get()
1387
+
1388
+ @size.setter
1389
+ def size(self, value):
1390
+ self.xl.font_size.set(value)
1391
+
1392
+ @property
1393
+ def color(self):
1394
+ if isinstance(self.parent, Range):
1395
+ return tuple(self.xl.color.get())
1396
+ elif isinstance(self.parent, Shape):
1397
+ return tuple(self.xl.font_color.get())
1398
+
1399
+ @color.setter
1400
+ def color(self, color_or_rgb):
1401
+ if self.xl is not None:
1402
+ if isinstance(self.parent, (Range, Characters)):
1403
+ obj = self.xl.color
1404
+ elif isinstance(self.parent, Shape):
1405
+ obj = self.xl.font_color
1406
+
1407
+ if isinstance(color_or_rgb, int):
1408
+ obj.set(int_to_rgb(color_or_rgb))
1409
+ else:
1410
+ obj.set(color_or_rgb)
1411
+
1412
+ @property
1413
+ def name(self):
1414
+ if isinstance(self.parent, Range):
1415
+ return self.xl.name.get()
1416
+ elif isinstance(self.parent, Shape):
1417
+ return self.xl.font_name.get()
1418
+
1419
+ @name.setter
1420
+ def name(self, value):
1421
+ if isinstance(self.parent, Range):
1422
+ self.xl.name.set(value)
1423
+ elif isinstance(self.parent, Shape):
1424
+ self.xl.font_name.set(value)
1425
+
1426
+
1427
+ class Characters(base_classes.Characters):
1428
+ def __init__(self, parent, xl):
1429
+ self.parent = parent
1430
+ self.xl = xl
1431
+
1432
+ @property
1433
+ def api(self):
1434
+ return self.xl
1435
+
1436
+ @property
1437
+ def text(self):
1438
+ return self.xl.content.get()
1439
+
1440
+ @property
1441
+ def font(self):
1442
+ return Font(self, self.xl.font_object)
1443
+
1444
+ def __getitem__(self, item):
1445
+ # TODO: This is broken with AppleScript and Excel 2016:
1446
+ # set bold of font object of (characters 5 thru 9 of range "A1") to true
1447
+ # https://answers.microsoft.com/en-us/msoffice/forum/
1448
+ # msoffice_excel-mso_mac-msoversion_other/applescript-and-excel-problem/
1449
+ # 6e5a50b1-6209-4fbf-91f4-6d6674f1e488
1450
+ if isinstance(item, slice):
1451
+ return Characters(
1452
+ parent=self.parent,
1453
+ xl=self.xl[
1454
+ item.start + 1
1455
+ if item.start
1456
+ else None : item.stop
1457
+ if item.stop
1458
+ else len(self.text)
1459
+ ],
1460
+ )
1461
+ else:
1462
+ return Characters(parent=self.parent, xl=self.xl[item + 1 : item + 1])
1463
+
1464
+
1465
+ class PageSetup(base_classes.PageSetup):
1466
+ def __init__(self, parent, xl):
1467
+ self.parent = parent
1468
+ self.xl = xl
1469
+
1470
+ @property
1471
+ def api(self):
1472
+ return self.xl
1473
+
1474
+ @property
1475
+ def print_area(self):
1476
+ value = self.xl.print_area.get()
1477
+ if value == kw.missing_value:
1478
+ return None
1479
+ else:
1480
+ return self.xl.print_area.get()
1481
+
1482
+ @print_area.setter
1483
+ def print_area(self, value):
1484
+ self.xl.print_area.set("" if value is None else value)
1485
+
1486
+
1487
+ class Note(base_classes.Note):
1488
+ def __init__(self, parent, xl):
1489
+ self.parent = parent
1490
+ self.xl = xl
1491
+
1492
+ def api(self):
1493
+ return self.xl
1494
+
1495
+ @property
1496
+ def text(self):
1497
+ return self.xl.Excel_comment_text()
1498
+
1499
+ @text.setter
1500
+ def text(self, value):
1501
+ self.xl.Excel_comment_text(text=value)
1502
+
1503
+ def delete(self):
1504
+ self.parent.xl.clear_Excel_comments()
1505
+
1506
+
1507
+ class Collection(base_classes.Collection):
1508
+ def __init__(self, parent):
1509
+ self._parent = parent
1510
+ self.xl = getattr(self.parent.xl, self._attr)
1511
+
1512
+ @property
1513
+ def parent(self):
1514
+ return self._parent
1515
+
1516
+ @property
1517
+ def api(self):
1518
+ return self.xl
1519
+
1520
+ def __call__(self, key):
1521
+ if not self.xl[key].exists():
1522
+ raise KeyError(key)
1523
+ return self._wrap(self.parent, key)
1524
+
1525
+ def __len__(self):
1526
+ return self.parent.xl.count(each=self._kw)
1527
+
1528
+ def __iter__(self):
1529
+ for i in range(len(self)):
1530
+ yield self(i + 1)
1531
+
1532
+ def __contains__(self, key):
1533
+ return self.xl[key].exists()
1534
+
1535
+
1536
+ class Table(base_classes.Table):
1537
+ def __init__(self, parent, key):
1538
+ self._parent = parent
1539
+ self.xl = parent.xl.list_objects[key]
1540
+
1541
+ @property
1542
+ def parent(self):
1543
+ return self._parent
1544
+
1545
+ @property
1546
+ def api(self):
1547
+ return self.xl
1548
+
1549
+ @property
1550
+ def name(self):
1551
+ return self.xl.name.get()
1552
+
1553
+ @name.setter
1554
+ def name(self, value):
1555
+ self.xl.name.set(value)
1556
+ self.xl = self.parent.xl.list_objects[value]
1557
+
1558
+ @property
1559
+ def data_body_range(self):
1560
+ if self.xl.cell_table.get() == kw.missing_value:
1561
+ return
1562
+ else:
1563
+ return Range(self.parent, self.xl.cell_table.get_address())
1564
+
1565
+ @property
1566
+ def display_name(self):
1567
+ return self.xl.display_name.get()
1568
+
1569
+ @display_name.setter
1570
+ def display_name(self, value):
1571
+ # Changing the display_name also changes the name
1572
+ self.xl.display_name.set(value)
1573
+ self.xl = self.parent.xl.list_objects[value]
1574
+
1575
+ @property
1576
+ def header_row_range(self):
1577
+ if self.xl.header_row.get() == kw.missing_value:
1578
+ return
1579
+ else:
1580
+ return Range(self.parent, self.xl.header_row.get_address())
1581
+
1582
+ @property
1583
+ def insert_row_range(self):
1584
+ if self.xl.insert_row.get() == kw.missing_value:
1585
+ return
1586
+ else:
1587
+ return Range(self.parent, self.xl.insert_row.get_address())
1588
+
1589
+ @property
1590
+ def range(self):
1591
+ return Range(self.parent, self.xl.range_object.get_address())
1592
+
1593
+ @property
1594
+ def show_autofilter(self):
1595
+ return self.xl.show_autofilter.get()
1596
+
1597
+ @show_autofilter.setter
1598
+ def show_autofilter(self, value):
1599
+ self.xl.show_autofilter.set(value)
1600
+
1601
+ @property
1602
+ def show_headers(self):
1603
+ return self.xl.show_headers.get()
1604
+
1605
+ @show_headers.setter
1606
+ def show_headers(self, value):
1607
+ self.xl.show_headers.set(value)
1608
+
1609
+ @property
1610
+ def show_table_style_column_stripes(self):
1611
+ return self.xl.show_table_style_column_stripes.get()
1612
+
1613
+ @show_table_style_column_stripes.setter
1614
+ def show_table_style_column_stripes(self, value):
1615
+ self.xl.show_table_style_column_stripes.set(value)
1616
+
1617
+ @property
1618
+ def show_table_style_first_column(self):
1619
+ return self.xl.show_table_style_first_column.get()
1620
+
1621
+ @show_table_style_first_column.setter
1622
+ def show_table_style_first_column(self, value):
1623
+ self.xl.show_table_style_first_column.set(value)
1624
+
1625
+ @property
1626
+ def show_table_style_last_column(self):
1627
+ return self.xl.show_table_style_last_column.get()
1628
+
1629
+ @show_table_style_last_column.setter
1630
+ def show_table_style_last_column(self, value):
1631
+ self.xl.show_table_style_last_column.set(value)
1632
+
1633
+ @property
1634
+ def show_table_style_row_stripes(self):
1635
+ return self.xl.show_table_style_row_stripes.get()
1636
+
1637
+ @show_table_style_row_stripes.setter
1638
+ def show_table_style_row_stripes(self, value):
1639
+ self.xl.show_table_style_row_stripes.set(value)
1640
+
1641
+ @property
1642
+ def show_totals(self):
1643
+ return self.xl.total.get()
1644
+
1645
+ @show_totals.setter
1646
+ def show_totals(self, value):
1647
+ self.xl.total.set(value)
1648
+
1649
+ @property
1650
+ def table_style(self):
1651
+ return self.xl.table_style.properties().get(kw.name)
1652
+
1653
+ @table_style.setter
1654
+ def table_style(self, value):
1655
+ self.xl.table_style.set(value)
1656
+
1657
+ @property
1658
+ def totals_row_range(self):
1659
+ if self.xl.total_row.get() == kw.missing_value:
1660
+ return
1661
+ else:
1662
+ return Range(self.parent, self.xl.total_row.get_address())
1663
+
1664
+ def resize(self, range):
1665
+ self.xl.resize(range=range.api)
1666
+
1667
+
1668
+ class Tables(Collection, base_classes.Tables):
1669
+ _attr = "list_objects"
1670
+ _kw = kw.list_object
1671
+ _wrap = Table
1672
+
1673
+ def add(
1674
+ self,
1675
+ source_type=None,
1676
+ source=None,
1677
+ link_source=None,
1678
+ has_headers=None,
1679
+ destination=None,
1680
+ table_style_name=None,
1681
+ name=None,
1682
+ ):
1683
+ header_row = {
1684
+ True: kw.header_yes,
1685
+ False: kw.header_no,
1686
+ "guess": kw.header_guess,
1687
+ }
1688
+ sheet_index = self.parent.xl.entry_index.get()
1689
+ table = Table(
1690
+ self.parent,
1691
+ self.parent.xl.make(
1692
+ at=self.parent.book.xl.sheets[sheet_index],
1693
+ new=kw.list_object,
1694
+ with_properties={
1695
+ kw.source_type: kw.src_range,
1696
+ kw.range_object: source.api,
1697
+ kw.header_row: header_row[has_headers],
1698
+ kw.table_style: table_style_name,
1699
+ },
1700
+ ).name.get(),
1701
+ )
1702
+ if name is not None:
1703
+ table.name = name
1704
+ return table
1705
+
1706
+
1707
+ class Chart(base_classes.Chart):
1708
+ def __init__(self, parent, key):
1709
+ self._parent = parent
1710
+ if isinstance(parent, Sheet):
1711
+ self.xl_obj = parent.xl.chart_objects[key]
1712
+ self.xl = self.xl_obj.chart
1713
+ else:
1714
+ self.xl_obj = None
1715
+ self.xl = self.charts[key]
1716
+
1717
+ @property
1718
+ def parent(self):
1719
+ return self._parent
1720
+
1721
+ @property
1722
+ def api(self):
1723
+ return self.xl_obj, self.xl
1724
+
1725
+ def set_source_data(self, rng):
1726
+ self.xl.set_source_data(source=rng.xl)
1727
+
1728
+ @property
1729
+ def name(self):
1730
+ if self.xl_obj is not None:
1731
+ return self.xl_obj.name.get()
1732
+ else:
1733
+ return self.xl.name.get()
1734
+
1735
+ @name.setter
1736
+ def name(self, value):
1737
+ if self.xl_obj is not None:
1738
+ self.xl_obj.name.set(value)
1739
+ else:
1740
+ self.xl.name.get(value)
1741
+
1742
+ @property
1743
+ def chart_type(self):
1744
+ return chart_types_k2s[self.xl.chart_type.get()]
1745
+
1746
+ @chart_type.setter
1747
+ def chart_type(self, value):
1748
+ self.xl.chart_type.set(chart_types_s2k[value])
1749
+
1750
+ @property
1751
+ def left(self):
1752
+ if self.xl_obj is None:
1753
+ raise Exception("This chart is not embedded.")
1754
+ return self.xl_obj.left_position.get()
1755
+
1756
+ @left.setter
1757
+ def left(self, value):
1758
+ if self.xl_obj is None:
1759
+ raise Exception("This chart is not embedded.")
1760
+ self.xl_obj.left_position.set(value)
1761
+
1762
+ @property
1763
+ def top(self):
1764
+ if self.xl_obj is None:
1765
+ raise Exception("This chart is not embedded.")
1766
+ return self.xl_obj.top.get()
1767
+
1768
+ @top.setter
1769
+ def top(self, value):
1770
+ if self.xl_obj is None:
1771
+ raise Exception("This chart is not embedded.")
1772
+ self.xl_obj.top.set(value)
1773
+
1774
+ @property
1775
+ def width(self):
1776
+ if self.xl_obj is None:
1777
+ raise Exception("This chart is not embedded.")
1778
+ return self.xl_obj.width.get()
1779
+
1780
+ @width.setter
1781
+ def width(self, value):
1782
+ if self.xl_obj is None:
1783
+ raise Exception("This chart is not embedded.")
1784
+ self.xl_obj.width.set(value)
1785
+
1786
+ @property
1787
+ def height(self):
1788
+ if self.xl_obj is None:
1789
+ raise Exception("This chart is not embedded.")
1790
+ return self.xl_obj.height.get()
1791
+
1792
+ @height.setter
1793
+ def height(self, value):
1794
+ if self.xl_obj is None:
1795
+ raise Exception("This chart is not embedded.")
1796
+ self.xl_obj.height.set(value)
1797
+
1798
+ def delete(self):
1799
+ self.xl_obj.delete()
1800
+
1801
+ def to_png(self, path):
1802
+ raise xlwings.XlwingsError("Chart.to_png() isn't supported on macOS.")
1803
+ # Both versions should work, but seem to be broken with Excel 2016
1804
+ #
1805
+ # Version 1
1806
+ # import uuid
1807
+ # temp_path = posix_to_hfs_path(os.path.expanduser("~")
1808
+ # + f"/Library/Containers/com.microsoft.Excel/"
1809
+ # f"Data/{uuid.uuid4()}.png")
1810
+ # self.xl.save_as(filename=temp_path)
1811
+ # shutil.copy2(temp_path, path)
1812
+ # try:
1813
+ # os.unlink(temp_path)
1814
+ # except:
1815
+ # pass
1816
+ #
1817
+ # Version 2
1818
+ # self.xl_obj.save_as_picture(file_name=posix_to_hfs_path('...'),
1819
+ # picture_type=kw.save_as_PNG_file)
1820
+
1821
+ def to_pdf(self, path, quality=None):
1822
+ raise xlwings.XlwingsError("Chart.to_pdf() isn't supported on macOS.")
1823
+
1824
+
1825
+ class Charts(Collection, base_classes.Charts):
1826
+ _attr = "chart_objects"
1827
+ _kw = kw.chart_object
1828
+ _wrap = Chart
1829
+
1830
+ def add(self, left, top, width, height):
1831
+ sheet_index = self.parent.xl.entry_index.get()
1832
+ return Chart(
1833
+ self.parent,
1834
+ self.parent.xl.make(
1835
+ at=self.parent.book.xl.sheets[sheet_index],
1836
+ new=kw.chart_object,
1837
+ with_properties={
1838
+ kw.width: width,
1839
+ kw.top: top,
1840
+ kw.left_position: left,
1841
+ kw.height: height,
1842
+ },
1843
+ ).name.get(),
1844
+ )
1845
+
1846
+
1847
+ class Picture(base_classes.Picture):
1848
+ def __init__(self, parent, key):
1849
+ self._parent = parent
1850
+ self.xl = parent.xl.pictures[key]
1851
+
1852
+ @property
1853
+ def parent(self):
1854
+ return self._parent
1855
+
1856
+ @property
1857
+ def api(self):
1858
+ return self.xl
1859
+
1860
+ @property
1861
+ def name(self):
1862
+ return self.xl.name.get()
1863
+
1864
+ @name.setter
1865
+ def name(self, value):
1866
+ self.xl.name.set(value)
1867
+
1868
+ @property
1869
+ def left(self):
1870
+ return self.xl.left_position.get()
1871
+
1872
+ @left.setter
1873
+ def left(self, value):
1874
+ self.xl.left_position.set(value)
1875
+
1876
+ @property
1877
+ def top(self):
1878
+ return self.xl.top.get()
1879
+
1880
+ @top.setter
1881
+ def top(self, value):
1882
+ self.xl.top.set(value)
1883
+
1884
+ @property
1885
+ def width(self):
1886
+ return self.xl.width.get()
1887
+
1888
+ @width.setter
1889
+ def width(self, value):
1890
+ self.xl.width.set(value)
1891
+
1892
+ @property
1893
+ def height(self):
1894
+ return self.xl.height.get()
1895
+
1896
+ @height.setter
1897
+ def height(self, value):
1898
+ self.xl.height.set(value)
1899
+
1900
+ def delete(self):
1901
+ self.xl.delete()
1902
+
1903
+ @property
1904
+ def lock_aspect_ratio(self):
1905
+ return self.xl.lock_aspect_ratio.get()
1906
+
1907
+ @lock_aspect_ratio.setter
1908
+ def lock_aspect_ratio(self, value):
1909
+ self.xl.lock_aspect_ratio.set(value)
1910
+
1911
+ def update(self, filename):
1912
+ return utils.excel_update_picture(self, filename)
1913
+
1914
+
1915
+ class Pictures(Collection, base_classes.Pictures):
1916
+ _attr = "pictures"
1917
+ _kw = kw.picture
1918
+ _wrap = Picture
1919
+
1920
+ def add(
1921
+ self,
1922
+ filename,
1923
+ link_to_file,
1924
+ save_with_document,
1925
+ left,
1926
+ top,
1927
+ width,
1928
+ height,
1929
+ anchor,
1930
+ ):
1931
+ if anchor:
1932
+ top, left = anchor.top, anchor.left
1933
+
1934
+ version = VersionNumber(self.parent.book.app.version)
1935
+
1936
+ if not link_to_file and version >= 15:
1937
+ # Office 2016 for Mac is sandboxed. This path seems to work without the
1938
+ # need of granting access explicitly.
1939
+ xlwings_picture = (
1940
+ os.path.expanduser("~")
1941
+ + "/Library/Containers/com.microsoft.Excel/Data/xlwings_picture.png"
1942
+ )
1943
+ shutil.copy2(filename, xlwings_picture)
1944
+ filename = xlwings_picture
1945
+
1946
+ sheet_index = self.parent.xl.entry_index.get()
1947
+ picture = Picture(
1948
+ self.parent,
1949
+ self.parent.xl.make(
1950
+ at=self.parent.book.xl.sheets[sheet_index],
1951
+ new=kw.picture,
1952
+ with_properties={
1953
+ kw.file_name: posix_to_hfs_path(filename),
1954
+ kw.link_to_file: link_to_file,
1955
+ kw.save_with_document: save_with_document,
1956
+ kw.width: width,
1957
+ kw.height: height,
1958
+ # Top and left: see below
1959
+ kw.top: 0,
1960
+ kw.left_position: 0,
1961
+ },
1962
+ ).name.get(),
1963
+ )
1964
+
1965
+ # Top and left cause an issue in the make command above
1966
+ # if they are not set to 0 when width & height are -1
1967
+ picture.top = top if top else 0
1968
+ picture.left = left if left else 0
1969
+
1970
+ if not link_to_file and version >= 15:
1971
+ os.remove(filename)
1972
+
1973
+ return picture
1974
+
1975
+
1976
+ class Names(base_classes.Names):
1977
+ def __init__(self, parent, xl):
1978
+ self.parent = parent
1979
+ self.xl = xl
1980
+
1981
+ def __call__(self, name_or_index):
1982
+ return Name(self.parent, xl=self.xl[name_or_index])
1983
+
1984
+ def contains(self, name_or_index):
1985
+ try:
1986
+ self.xl[name_or_index].get()
1987
+ except appscript.reference.CommandError:
1988
+ # TODO: make more specific
1989
+ return False
1990
+ return True
1991
+
1992
+ def __len__(self):
1993
+ named_items = self.xl.get()
1994
+ if named_items == kw.missing_value:
1995
+ return 0
1996
+ else:
1997
+ return len(named_items)
1998
+
1999
+ def add(self, name, refers_to):
2000
+ return Name(
2001
+ self.parent,
2002
+ self.parent.xl.make(
2003
+ at=self.parent.xl,
2004
+ new=kw.named_item,
2005
+ with_properties={kw.references: refers_to, kw.name: name},
2006
+ ),
2007
+ )
2008
+
2009
+
2010
+ class Name(base_classes.Name):
2011
+ def __init__(self, parent, xl):
2012
+ self.parent = parent
2013
+ self.xl = xl
2014
+
2015
+ def delete(self):
2016
+ self.xl.delete()
2017
+
2018
+ @property
2019
+ def name(self):
2020
+ return self.xl.name.get()
2021
+
2022
+ @name.setter
2023
+ def name(self, value):
2024
+ self.xl.name.set(value)
2025
+
2026
+ @property
2027
+ def refers_to(self):
2028
+ return self.xl.properties().get(kw.references)
2029
+
2030
+ @refers_to.setter
2031
+ def refers_to(self, value):
2032
+ self.xl.properties(kw.references).set(value)
2033
+
2034
+ @property
2035
+ def refers_to_range(self):
2036
+ book = self.parent if isinstance(self.parent, Book) else self.parent.book
2037
+ external_address = self.xl.reference_range.get_address(external=True)
2038
+ match = re.search(r"\](.*?)'?!(.*)", external_address)
2039
+ return Range(Sheet(book, match.group(1)), match.group(2))
2040
+
2041
+
2042
+ class Shapes(Collection):
2043
+ _attr = "shapes"
2044
+ _kw = kw.shape
2045
+ _wrap = Shape
2046
+
2047
+
2048
+ @atexit.register
2049
+ def cleanup():
2050
+ """
2051
+ Since AppleScript cannot access Excel while a Macro is running, we have to run the
2052
+ Python call in a background process which makes the call return immediately: we
2053
+ rely on the StatusBar to give the user feedback.
2054
+ This function is triggered when the interpreter exits and runs the CleanUp Macro in
2055
+ VBA to show any errors and to reset the StatusBar.
2056
+ """
2057
+ if is_excel_running():
2058
+ # Prevents Excel from reopening
2059
+ # if it has been closed manually or never been opened
2060
+ for app in Apps():
2061
+ try:
2062
+ app.xl.run_VB_macro("CleanUp")
2063
+ except (CommandError, AttributeError, aem.aemsend.EventError):
2064
+ # Excel files initiated from Python don't have the xlwings VBA module
2065
+ pass
2066
+
2067
+
2068
+ def posix_to_hfs_path(posix_path):
2069
+ """
2070
+ Turns a posix path (/Path/file.ext) into an HFS path (Macintosh HD:Path:file.ext)
2071
+ """
2072
+ dir_name, file_name = os.path.split(posix_path)
2073
+ dir_name_hfs = mactypes.Alias(dir_name).hfspath
2074
+ return dir_name_hfs + ":" + file_name
2075
+
2076
+
2077
+ def hfs_to_posix_path(hfs_path):
2078
+ """
2079
+ Turns an HFS path (Macintosh HD:Path:file.ext) into a posix path (/Path/file.ext)
2080
+ """
2081
+ url = mactypes.convertpathtourl(hfs_path, 1) # kCFURLHFSPathStyle = 1
2082
+ return mactypes.converturltopath(url, 0) # kCFURLPOSIXPathStyle = 0
2083
+
2084
+
2085
+ def is_excel_running():
2086
+ for proc in psutil.process_iter():
2087
+ try:
2088
+ if proc.name() == "Microsoft Excel":
2089
+ return True
2090
+ except psutil.NoSuchProcess:
2091
+ pass
2092
+ return False
2093
+
2094
+
2095
+ # --- constants ---
2096
+
2097
+ chart_types_k2s = {
2098
+ kw.ThreeD_area: "3d_area",
2099
+ kw.ThreeD_area_stacked: "3d_area_stacked",
2100
+ kw.ThreeD_area_stacked_100: "3d_area_stacked_100",
2101
+ kw.ThreeD_bar_clustered: "3d_bar_clustered",
2102
+ kw.ThreeD_bar_stacked: "3d_bar_stacked",
2103
+ kw.ThreeD_bar_stacked_100: "3d_bar_stacked_100",
2104
+ kw.ThreeD_column: "3d_column",
2105
+ kw.ThreeD_column_clustered: "3d_column_clustered",
2106
+ kw.ThreeD_column_stacked: "3d_column_stacked",
2107
+ kw.ThreeD_column_stacked_100: "3d_column_stacked_100",
2108
+ kw.ThreeD_line: "3d_line",
2109
+ kw.ThreeD_pie: "3d_pie",
2110
+ kw.ThreeD_pie_exploded: "3d_pie_exploded",
2111
+ kw.area_chart: "area",
2112
+ kw.area_stacked: "area_stacked",
2113
+ kw.area_stacked_100: "area_stacked_100",
2114
+ kw.bar_clustered: "bar_clustered",
2115
+ kw.bar_of_pie: "bar_of_pie",
2116
+ kw.bar_stacked: "bar_stacked",
2117
+ kw.bar_stacked_100: "bar_stacked_100",
2118
+ kw.bubble: "bubble",
2119
+ kw.bubble_ThreeD_effect: "bubble_3d_effect",
2120
+ kw.column_clustered: "column_clustered",
2121
+ kw.column_stacked: "column_stacked",
2122
+ kw.column_stacked_100: "column_stacked_100",
2123
+ kw.combination_chart: "combination",
2124
+ kw.cone_bar_clustered: "cone_bar_clustered",
2125
+ kw.cone_bar_stacked: "cone_bar_stacked",
2126
+ kw.cone_bar_stacked_100: "cone_bar_stacked_100",
2127
+ kw.cone_col: "cone_col",
2128
+ kw.cone_column_clustered: "cone_col_clustered",
2129
+ kw.cone_column_stacked: "cone_col_stacked",
2130
+ kw.cone_column_stacked_100: "cone_col_stacked_100",
2131
+ kw.cylinder_bar_clustered: "cylinder_bar_clustered",
2132
+ kw.cylinder_bar_stacked: "cylinder_bar_stacked",
2133
+ kw.cylinder_bar_stacked_100: "cylinder_bar_stacked_100",
2134
+ kw.cylinder_column: "cylinder_col",
2135
+ kw.cylinder_column_clustered: "cylinder_col_clustered",
2136
+ kw.cylinder_column_stacked: "cylinder_col_stacked",
2137
+ kw.cylinder_column_stacked_100: "cylinder_col_stacked_100",
2138
+ kw.doughnut: "doughnut",
2139
+ kw.doughnut_exploded: "doughnut_exploded",
2140
+ kw.line_chart: "line",
2141
+ kw.line_markers: "line_markers",
2142
+ kw.line_markers_stacked: "line_markers_stacked",
2143
+ kw.line_markers_stacked_100: "line_markers_stacked_100",
2144
+ kw.line_stacked: "line_stacked",
2145
+ kw.line_stacked_100: "line_stacked_100",
2146
+ kw.pie_chart: "pie",
2147
+ kw.pie_exploded: "pie_exploded",
2148
+ kw.pie_of_pie: "pie_of_pie",
2149
+ kw.pyramid_bar_clustered: "pyramid_bar_clustered",
2150
+ kw.pyramid_bar_stacked: "pyramid_bar_stacked",
2151
+ kw.pyramid_bar_stacked_100: "pyramid_bar_stacked_100",
2152
+ kw.pyramid_column: "pyramid_col",
2153
+ kw.pyramid_column_clustered: "pyramid_col_clustered",
2154
+ kw.pyramid_column_stacked: "pyramid_col_stacked",
2155
+ kw.pyramid_column_stacked_100: "pyramid_col_stacked_100",
2156
+ kw.radar: "radar",
2157
+ kw.radar_filled: "radar_filled",
2158
+ kw.radar_markers: "radar_markers",
2159
+ kw.stock_HLC: "stock_hlc",
2160
+ kw.stock_OHLC: "stock_ohlc",
2161
+ kw.stock_VHLC: "stock_vhlc",
2162
+ kw.stock_VOHLC: "stock_vohlc",
2163
+ kw.surface: "surface",
2164
+ kw.surface_top_view: "surface_top_view",
2165
+ kw.surface_top_view_wireframe: "surface_top_view_wireframe",
2166
+ kw.surface_wireframe: "surface_wireframe",
2167
+ kw.xy_scatter_lines: "xy_scatter_lines",
2168
+ kw.xy_scatter_lines_no_markers: "xy_scatter_lines_no_markers",
2169
+ kw.xy_scatter_smooth: "xy_scatter_smooth",
2170
+ kw.xy_scatter_smooth_no_markers: "xy_scatter_smooth_no_markers",
2171
+ kw.xyscatter: "xy_scatter",
2172
+ }
2173
+
2174
+ chart_types_s2k = {v: k for k, v in chart_types_k2s.items()}
2175
+
2176
+ directions_s2k = {
2177
+ "d": kw.toward_the_bottom,
2178
+ "down": kw.toward_the_bottom,
2179
+ "l": kw.toward_the_left,
2180
+ "left": kw.toward_the_left,
2181
+ "r": kw.toward_the_right,
2182
+ "right": kw.toward_the_right,
2183
+ "u": kw.toward_the_top,
2184
+ "up": kw.toward_the_top,
2185
+ }
2186
+
2187
+ directions_k2s = {
2188
+ kw.toward_the_bottom: "down",
2189
+ kw.toward_the_left: "left",
2190
+ kw.toward_the_right: "right",
2191
+ kw.toward_the_top: "up",
2192
+ }
2193
+
2194
+ calculation_k2s = {
2195
+ kw.calculation_automatic: "automatic",
2196
+ kw.calculation_manual: "manual",
2197
+ kw.calculation_semiautomatic: "semiautomatic",
2198
+ }
2199
+
2200
+ calculation_s2k = {v: k for k, v in calculation_k2s.items()}
2201
+
2202
+ shape_types_k2s = {
2203
+ kw.shape_type_3d_model: "3d_model",
2204
+ kw.shape_type_auto: "auto_shape",
2205
+ kw.shape_type_callout: "callout",
2206
+ kw.shape_type_canvas: "canvas",
2207
+ kw.shape_type_chart: "chart",
2208
+ kw.shape_type_comment: "comment",
2209
+ kw.shape_type_content_application: "content_app",
2210
+ kw.shape_type_diagram: "diagram",
2211
+ kw.shape_type_free_form: "free_form",
2212
+ kw.shape_type_graphic: "graphic",
2213
+ kw.shape_type_group: "group",
2214
+ kw.shape_type_embedded_OLE_control: "embedded_ole_object",
2215
+ kw.shape_type_form_control: "form_control",
2216
+ kw.shape_type_line: "line",
2217
+ kw.shape_type_linked_3d_model: "linked_3d_model",
2218
+ kw.shape_type_linked_graphic: "linked_graphic",
2219
+ kw.shape_type_linked_OLE_object: "linked_ole_object",
2220
+ kw.shape_type_linked_picture: "linked_picture",
2221
+ kw.shape_type_OLE_control: "ole_control_object",
2222
+ kw.shape_type_picture: "picture",
2223
+ kw.shape_type_place_holder: "placeholder",
2224
+ kw.shape_type_web_video: "web_video",
2225
+ kw.shape_type_media: "media",
2226
+ kw.shape_type_text_box: "text_box",
2227
+ kw.shape_type_table: "table",
2228
+ kw.shape_type_ink: "ink",
2229
+ kw.shape_type_ink_comment: "ink_comment",
2230
+ kw.shape_type_unset: "unset",
2231
+ kw.shape_type_slicer: "slicer",
2232
+ }
2233
+
2234
+ scaling = {
2235
+ "scale_from_top_left": kw.scale_from_top_left,
2236
+ "scale_from_bottom_right": kw.scale_from_bottom_right,
2237
+ "scale_from_middle": kw.scale_from_middle,
2238
+ }
2239
+
2240
+ shape_types_s2k = {v: k for k, v in shape_types_k2s.items()}