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,2518 @@
1
+ import atexit
2
+ import locale
3
+ import os
4
+ import subprocess
5
+ import sys
6
+ from shlex import split
7
+
8
+ # Hack to find pythoncom.dll - needed for some distribution/setups (includes seemingly
9
+ # unused import win32api) E.g. if python is started with the full path outside of the
10
+ # python path, then it almost certainly fails
11
+ cwd = os.getcwd()
12
+ if not hasattr(sys, "frozen"):
13
+ # cx_Freeze etc. will fail here otherwise
14
+ os.chdir(sys.exec_prefix)
15
+ # Since Python 3.8, pywintypes needs to be imported before win32api or you get
16
+ # ImportError: DLL load failed while importing win32api: The specified module could not
17
+ # be found.
18
+ # See: https://stackoverflow.com/questions/58805040/pywin32-226-and-virtual-environments
19
+ # Seems to be required even with pywin32 227
20
+ import pywintypes
21
+ import win32api
22
+ import win32con
23
+
24
+ os.chdir(cwd)
25
+
26
+ import ctypes
27
+ import datetime as dt
28
+ import numbers
29
+ import types
30
+ from ctypes import PyDLL, byref, oledll, py_object, windll
31
+ from pathlib import Path
32
+ from warnings import warn
33
+
34
+ import pythoncom
35
+
36
+ # Patching CoClassBaseClass, see https://github.com/xlwings/xlwings/issues/1789
37
+ import win32com.client
38
+
39
+ from ._win32patch import CoClassBaseClass
40
+
41
+ win32com.client.CoClassBaseClass = CoClassBaseClass
42
+ # End Patch
43
+
44
+ import win32gui
45
+ import win32process
46
+ import win32timezone
47
+ from win32com.client import (
48
+ CDispatch,
49
+ CoClassBaseClass,
50
+ Dispatch,
51
+ DispatchBaseClass,
52
+ DispatchEx,
53
+ )
54
+
55
+ import tdrpa._tdxlwings as xlwings
56
+
57
+ from . import base_classes, constants, utils
58
+ from .constants import (
59
+ ColorIndex,
60
+ DeleteShiftDirection,
61
+ FileFormat,
62
+ FixedFormatType,
63
+ HtmlType,
64
+ InsertFormatOrigin,
65
+ InsertShiftDirection,
66
+ ListObjectSourceType,
67
+ SourceType,
68
+ UpdateLinks,
69
+ )
70
+ from .utils import (
71
+ col_name,
72
+ fullname_url_to_local_path,
73
+ hex_to_rgb,
74
+ int_to_rgb,
75
+ np_datetime_to_datetime,
76
+ read_config_sheet,
77
+ rgb_to_int,
78
+ )
79
+
80
+ # Optional imports
81
+ try:
82
+ import pandas as pd
83
+ except ImportError:
84
+ pd = None
85
+ try:
86
+ import numpy as np
87
+ except ImportError:
88
+ np = None
89
+ try:
90
+ from PIL import ImageGrab
91
+ except ImportError:
92
+ PIL = None
93
+
94
+
95
+ time_types = (dt.date, dt.datetime, pywintypes.TimeType)
96
+ if np:
97
+ time_types = time_types + (np.datetime64,)
98
+
99
+
100
+ N_COM_ATTEMPTS = 0 # 0 means try indefinitely
101
+ BOOK_CALLER = None
102
+ missing = object()
103
+
104
+
105
+ @atexit.register
106
+ def cleanup():
107
+ """Clear up any zombie processes"""
108
+ try:
109
+ Apps.cleanup()
110
+ except: # noqa: E722
111
+ pass
112
+
113
+
114
+ class COMRetryMethodWrapper:
115
+ def __init__(self, method):
116
+ self.__method = method
117
+
118
+ def __call__(self, *args, **kwargs):
119
+ n_attempt = 1
120
+ while True:
121
+ try:
122
+ v = self.__method(*args, **kwargs)
123
+ if isinstance(v, (CDispatch, CoClassBaseClass, DispatchBaseClass)):
124
+ return COMRetryObjectWrapper(v)
125
+ elif isinstance(v, types.MethodType):
126
+ return COMRetryMethodWrapper(v)
127
+ else:
128
+ return v
129
+ except pywintypes.com_error as e:
130
+ if (
131
+ not N_COM_ATTEMPTS or n_attempt < N_COM_ATTEMPTS
132
+ ) and e.hresult == -2147418111:
133
+ n_attempt += 1
134
+ continue
135
+ else:
136
+ raise
137
+ except AttributeError:
138
+ if not N_COM_ATTEMPTS or n_attempt < N_COM_ATTEMPTS:
139
+ n_attempt += 1
140
+ continue
141
+ else:
142
+ raise
143
+
144
+
145
+ class ExcelBusyError(Exception):
146
+ def __init__(self):
147
+ super(ExcelBusyError, self).__init__("Excel application is not responding")
148
+
149
+
150
+ class COMRetryObjectWrapper:
151
+ def __init__(self, inner):
152
+ object.__setattr__(self, "_inner", inner)
153
+
154
+ def __repr__(self):
155
+ return repr(self._inner)
156
+
157
+ def __setattr__(self, key, value):
158
+ n_attempt = 1
159
+ while True:
160
+ try:
161
+ return setattr(self._inner, key, value)
162
+ except pywintypes.com_error as e:
163
+ hresult, msg, exc, arg = e.args
164
+ if exc:
165
+ wcode, source, text, help_file, help_id, scode = exc
166
+ else:
167
+ wcode, source, text, help_file, help_id, scode = ( # noqa: F841
168
+ None,
169
+ None,
170
+ None,
171
+ None,
172
+ None,
173
+ None,
174
+ )
175
+ # -2147352567 is the error you get when clicking into cells. If we
176
+ # wouldn't check for scode, actions like renaming a sheet with >31
177
+ # characters would be tried forever, causing xlwings to hang (they
178
+ # also have hresult -2147352567).
179
+ if (
180
+ (not N_COM_ATTEMPTS or n_attempt < N_COM_ATTEMPTS)
181
+ and e.hresult in [-2147418111, -2147352567]
182
+ and scode in [None, -2146777998]
183
+ ):
184
+ n_attempt += 1
185
+ continue
186
+ else:
187
+ raise
188
+ except AttributeError:
189
+ if not N_COM_ATTEMPTS or n_attempt < N_COM_ATTEMPTS:
190
+ n_attempt += 1
191
+ continue
192
+ else:
193
+ raise
194
+
195
+ def __getattr__(self, item):
196
+ n_attempt = 1
197
+ while True:
198
+ try:
199
+ v = getattr(self._inner, item)
200
+ if isinstance(v, (CDispatch, CoClassBaseClass, DispatchBaseClass)):
201
+ return COMRetryObjectWrapper(v)
202
+ elif isinstance(v, types.MethodType):
203
+ return COMRetryMethodWrapper(v)
204
+ else:
205
+ return v
206
+ except pywintypes.com_error as e:
207
+ if (
208
+ not N_COM_ATTEMPTS or n_attempt < N_COM_ATTEMPTS
209
+ ) and e.hresult == -2147418111:
210
+ n_attempt += 1
211
+ continue
212
+ else:
213
+ raise
214
+ except AttributeError:
215
+ # pywin32 reacts incorrectly to RPC_E_CALL_REJECTED (i.e. assumes
216
+ # attribute doesn't exist, thus not allowing to distinguish between
217
+ # cases where attribute really doesn't exist or error is only being
218
+ # thrown because the COM RPC server is busy). Here we try to test to
219
+ # see what's going on really
220
+ try:
221
+ self._oleobj_.GetIDsOfNames(0, item)
222
+ except pythoncom.ole_error as e:
223
+ if e.hresult != -2147418111: # RPC_E_CALL_REJECTED
224
+ # attribute probably really doesn't exist
225
+ raise
226
+ if not N_COM_ATTEMPTS or n_attempt < N_COM_ATTEMPTS:
227
+ n_attempt += 1
228
+ continue
229
+ else:
230
+ raise ExcelBusyError()
231
+
232
+ def __call__(self, *args, **kwargs):
233
+ n_attempt = 1
234
+ for i in range(N_COM_ATTEMPTS + 1):
235
+ try:
236
+ v = self._inner(*args, **kwargs)
237
+ if isinstance(v, (CDispatch, CoClassBaseClass, DispatchBaseClass)):
238
+ return COMRetryObjectWrapper(v)
239
+ elif isinstance(v, types.MethodType):
240
+ return COMRetryMethodWrapper(v)
241
+ else:
242
+ return v
243
+ except pywintypes.com_error as e:
244
+ if (
245
+ not N_COM_ATTEMPTS or n_attempt < N_COM_ATTEMPTS
246
+ ) and e.hresult == -2147418111:
247
+ n_attempt += 1
248
+ continue
249
+ else:
250
+ raise
251
+ except AttributeError:
252
+ if not N_COM_ATTEMPTS or n_attempt < N_COM_ATTEMPTS:
253
+ n_attempt += 1
254
+ continue
255
+ else:
256
+ raise
257
+
258
+ def __iter__(self):
259
+ for v in self._inner:
260
+ if isinstance(v, (CDispatch, CoClassBaseClass, DispatchBaseClass)):
261
+ yield COMRetryObjectWrapper(v)
262
+ else:
263
+ yield v
264
+
265
+
266
+ # Constants
267
+ OBJID_NATIVEOM = -16
268
+
269
+
270
+ class _GUID(ctypes.Structure):
271
+ # https://docs.microsoft.com/en-us/openspecs/windows_protocols/
272
+ # ms-dtyp/49e490b8-f972-45d6-a3a4-99f924998d97
273
+ _fields_ = [
274
+ ("Data1", ctypes.c_ulong),
275
+ ("Data2", ctypes.c_ushort),
276
+ ("Data3", ctypes.c_ushort),
277
+ ("Data4", ctypes.c_byte * 8),
278
+ ]
279
+
280
+
281
+ _IDISPATCH_GUID = _GUID()
282
+ oledll.ole32.CLSIDFromString(
283
+ "{00020400-0000-0000-C000-000000000046}", byref(_IDISPATCH_GUID)
284
+ )
285
+
286
+
287
+ def accessible_object_from_window(hwnd):
288
+ # ptr is a pointer to an IDispatch:
289
+ # https://docs.microsoft.com/en-us/windows/win32/api/oaidl/nn-oaidl-idispatch
290
+ # We don't bother using ctypes.POINTER(comtypes.automation.IDispatch)()
291
+ # because we won't dereference the pointer except through pywin32's
292
+ # pythoncom.PyCom_PyObjectFromIUnknown below in get_xl_app_from_hwnd().
293
+ ptr = ctypes.c_void_p()
294
+ res = oledll.oleacc.AccessibleObjectFromWindow( # noqa: F841
295
+ hwnd, OBJID_NATIVEOM, byref(_IDISPATCH_GUID), byref(ptr)
296
+ )
297
+ return ptr
298
+
299
+
300
+ def _checkDblpas(hwnd):
301
+ childWindow=win32gui.FindWindowEx(hwnd, 0, None, None)
302
+ if childWindow == 0:
303
+ return 0
304
+ pid = win32process.GetWindowThreadProcessId(childWindow)[1]
305
+ xldeskHwnd=win32gui.FindWindowEx(childWindow, 0, 'XLDESK', None)
306
+ if(xldeskHwnd==0):
307
+ xldeskHwnd=win32gui.FindWindowEx(hwnd, 0, 'XLDESK', None)
308
+ if xldeskHwnd==0:
309
+ handle = _checkDblpas(childWindow)
310
+ if handle == 0:
311
+ handlenext = windll.user32.GetWindow(childWindow,2)
312
+ if handlenext != 0:
313
+ while(1):
314
+ handletmp = _checkDblpas(handlenext)
315
+ if handletmp == 0:
316
+ handlenext = windll.user32.GetWindow(handlenext,2)
317
+ else:
318
+ return handletmp
319
+ else:
320
+ return 0
321
+ else:
322
+ return handle
323
+ else:
324
+ excel7Hwnd=win32gui.FindWindowEx(xldeskHwnd, 0, 'EXCEL7', None)
325
+ if excel7Hwnd:
326
+ return excel7Hwnd
327
+
328
+
329
+ def is_hwnd_xl_app(hwnd):
330
+ try:
331
+ child_hwnd = win32gui.FindWindowEx(hwnd, 0, "XLDESK", None)
332
+ child_hwnd = win32gui.FindWindowEx(child_hwnd, 0, "EXCEL7", None)
333
+ if child_hwnd == 0:
334
+ child_hwnd = _checkDblpas(hwnd)
335
+ ptr = accessible_object_from_window(child_hwnd) # noqa: F841
336
+ return True
337
+ except WindowsError:
338
+ return False
339
+ except pywintypes.error:
340
+ return False
341
+
342
+
343
+ _PyCom_PyObjectFromIUnknown = PyDLL(pythoncom.__file__).PyCom_PyObjectFromIUnknown
344
+ _PyCom_PyObjectFromIUnknown.restype = py_object
345
+
346
+
347
+ def get_xl_app_from_hwnd(hwnd):
348
+ pythoncom.CoInitialize()
349
+ child_hwnd = win32gui.FindWindowEx(hwnd, 0, "XLDESK", None)
350
+ child_hwnd = win32gui.FindWindowEx(child_hwnd, 0, "EXCEL7", None)
351
+ if child_hwnd == 0:
352
+ child_hwnd = _checkDblpas(hwnd)
353
+ ptr = accessible_object_from_window(child_hwnd)
354
+ p = _PyCom_PyObjectFromIUnknown(ptr, byref(_IDISPATCH_GUID), True)
355
+ disp = COMRetryObjectWrapper(Dispatch(p))
356
+ return disp.Application
357
+
358
+
359
+ def get_excel_hwnds():
360
+ pythoncom.CoInitialize()
361
+ hwnd = windll.user32.GetTopWindow(None)
362
+ pids = set()
363
+ while hwnd:
364
+ try:
365
+ # Apparently, this fails on some systems when Excel is closed
366
+ child_hwnd = win32gui.FindWindowEx(hwnd, 0, "XLDESK", None)
367
+ if child_hwnd:
368
+ child_hwnd = win32gui.FindWindowEx(child_hwnd, 0, "EXCEL7", None)
369
+ if child_hwnd:
370
+ pid = win32process.GetWindowThreadProcessId(hwnd)[1]
371
+ if pid not in pids:
372
+ pids.add(pid)
373
+ yield hwnd
374
+
375
+ child_hwnd = win32gui.FindWindowEx(hwnd, 0, 'XLMAIN', None)
376
+ if child_hwnd == 0 and win32gui.GetClassName(hwnd) == 'XLMAIN':
377
+ child_hwnd = hwnd
378
+ if child_hwnd:
379
+ child_hwnd = _checkDblpas(hwnd)
380
+ if child_hwnd:
381
+ pid = win32process.GetWindowThreadProcessId(hwnd)[1]
382
+ yield hwnd
383
+
384
+ except pywintypes.error:
385
+ pass
386
+
387
+ hwnd = windll.user32.GetWindow(hwnd, 2) # 2 = next window according to Z-order
388
+
389
+
390
+ def get_xl_apps():
391
+ for hwnd in get_excel_hwnds():
392
+ try:
393
+ yield get_xl_app_from_hwnd(hwnd)
394
+ except ExcelBusyError:
395
+ pass
396
+ except WindowsError:
397
+ # This happens if the bare Excel Application is open without Workbook, i.e.,
398
+ # there's no 'EXCEL7' child hwnd that would be necessary for a connection
399
+ pass
400
+
401
+
402
+ def is_range_instance(xl_range):
403
+ pyid = getattr(xl_range, "_oleobj_", None)
404
+ if pyid is None:
405
+ return False
406
+ return xl_range._oleobj_.GetTypeInfo().GetTypeAttr().iid == pywintypes.IID(
407
+ "{00020846-0000-0000-C000-000000000046}"
408
+ )
409
+ # return pyid.GetTypeInfo().GetDocumentation(-1)[0] == 'Range'
410
+
411
+
412
+ def _com_time_to_datetime(com_time, datetime_builder):
413
+ return datetime_builder(
414
+ month=com_time.month,
415
+ day=com_time.day,
416
+ year=com_time.year,
417
+ hour=com_time.hour,
418
+ minute=com_time.minute,
419
+ second=com_time.second,
420
+ microsecond=com_time.microsecond,
421
+ tzinfo=None,
422
+ )
423
+
424
+
425
+ def _datetime_to_com_time(dt_time):
426
+ """
427
+ This function is a modified version from Pyvot (https://pypi.python.org/pypi/Pyvot)
428
+ and subject to the following copyright:
429
+
430
+ Copyright (c) Microsoft Corporation.
431
+
432
+ This source code is subject to terms and conditions of the Apache License,
433
+ Version 2.0. A copy of the license can be found in the LICENSE.txt file at the root
434
+ of this distribution. If you cannot locate the Apache License, Version 2.0, please
435
+ send an email to vspython@microsoft.com. By using this source code in any fashion,
436
+ you are agreeing to be bound by the terms of the Apache License, Version 2.0.
437
+
438
+ You must not remove this notice, or any other, from this software.
439
+
440
+ """
441
+ # Convert date to datetime
442
+ if pd and isinstance(dt_time, type(pd.NaT)):
443
+ return ""
444
+ if np:
445
+ if type(dt_time) is np.datetime64:
446
+ dt_time = np_datetime_to_datetime(dt_time)
447
+
448
+ if type(dt_time) is dt.date:
449
+ dt_time = dt.datetime(
450
+ dt_time.year,
451
+ dt_time.month,
452
+ dt_time.day,
453
+ tzinfo=win32timezone.TimeZoneInfo.utc(),
454
+ )
455
+
456
+ # pywintypes has its time type inherit from datetime.
457
+ # For some reason, though it accepts plain datetimes, they must have a timezone set.
458
+ # See http://docs.activestate.com/activepython/2.7/pywin32/html/win32/help/py3k.html
459
+ # We replace no timezone -> UTC to allow round-trips in the naive case
460
+ if pd and isinstance(dt_time, pd.Timestamp):
461
+ # Otherwise pandas prints ignored exceptions on Python 3
462
+ dt_time = dt_time.to_pydatetime()
463
+ # We don't use pytz.utc to get rid of additional dependency
464
+ # Don't do any timezone transformation: simply cutoff the tz info
465
+ # If we don't reset it first, it gets transformed into UTC before sending to Excel
466
+ dt_time = dt_time.replace(tzinfo=None)
467
+ dt_time = dt_time.replace(tzinfo=win32timezone.TimeZoneInfo.utc())
468
+
469
+ return dt_time
470
+
471
+
472
+ cell_errors = {
473
+ -2146826281: "#DIV/0!",
474
+ -2146826246: "#N/A",
475
+ -2146826259: "#NAME?",
476
+ -2146826288: "#NULL!",
477
+ -2146826252: "#NUM!",
478
+ -2146826265: "#REF!",
479
+ -2146826273: "#VALUE!",
480
+ }
481
+
482
+
483
+ def _clean_value_data_element(
484
+ value, datetime_builder, empty_as, number_builder, err_to_str
485
+ ):
486
+ if value in ("", None):
487
+ return empty_as
488
+ elif isinstance(value, time_types):
489
+ return _com_time_to_datetime(value, datetime_builder)
490
+ elif number_builder is not None and isinstance(value, float):
491
+ value = number_builder(value)
492
+ elif isinstance(value, int) and value in cell_errors:
493
+ if err_to_str:
494
+ return cell_errors[value]
495
+ else:
496
+ return None
497
+ return value
498
+
499
+
500
+ class Engine:
501
+ @property
502
+ def apps(self):
503
+ return Apps()
504
+
505
+ @property
506
+ def name(self):
507
+ return "excel"
508
+
509
+ @property
510
+ def type(self):
511
+ return "desktop"
512
+
513
+ @staticmethod
514
+ def prepare_xl_data_element(x, date_format):
515
+ if isinstance(x, time_types):
516
+ return _datetime_to_com_time(x)
517
+ elif pd and pd.isna(x):
518
+ return ""
519
+ elif np and isinstance(x, (np.floating, float)) and np.isnan(x):
520
+ return ""
521
+ elif np and isinstance(x, np.number):
522
+ return float(x)
523
+ elif x is None:
524
+ return ""
525
+ else:
526
+ return x
527
+
528
+ @staticmethod
529
+ def clean_value_data(data, datetime_builder, empty_as, number_builder, err_to_str):
530
+ return [
531
+ [
532
+ _clean_value_data_element(
533
+ c, datetime_builder, empty_as, number_builder, err_to_str
534
+ )
535
+ for c in row
536
+ ]
537
+ for row in data
538
+ ]
539
+
540
+
541
+ engine = Engine()
542
+
543
+
544
+ class Apps(base_classes.Apps):
545
+ def keys(self):
546
+ k = []
547
+ for hwnd in get_excel_hwnds():
548
+ k.append(App(xl=hwnd).pid)
549
+ return k
550
+
551
+ def add(self, spec=None, add_book=None, xl=None, visible=None):
552
+ return App(spec=spec, add_book=add_book, xl=xl, visible=visible)
553
+
554
+ @staticmethod
555
+ def cleanup():
556
+ res = subprocess.run(
557
+ split('tasklist /FI "IMAGENAME eq EXCEL.exe"'),
558
+ stdout=subprocess.PIPE,
559
+ stderr=subprocess.STDOUT,
560
+ creationflags=subprocess.CREATE_NO_WINDOW,
561
+ encoding=locale.getpreferredencoding(),
562
+ )
563
+
564
+ all_pids = set()
565
+ for line in res.stdout.splitlines()[3:]:
566
+ # Ignored if there's no processes as it prints only 1 line
567
+ _, pid, _, _, _, _ = line.split()
568
+ all_pids.add(int(pid))
569
+
570
+ active_pids = {app.pid for app in xlwings.apps}
571
+ zombie_pids = all_pids - active_pids
572
+
573
+ for pid in zombie_pids:
574
+ subprocess.run(
575
+ split(f"taskkill /PID {pid} /F"),
576
+ stdout=subprocess.PIPE,
577
+ stderr=subprocess.STDOUT,
578
+ creationflags=subprocess.CREATE_NO_WINDOW,
579
+ encoding=locale.getpreferredencoding(),
580
+ )
581
+
582
+ def __iter__(self):
583
+ for hwnd in get_excel_hwnds():
584
+ yield App(xl=hwnd)
585
+
586
+ def __len__(self):
587
+ return len(list(get_excel_hwnds()))
588
+
589
+ def __getitem__(self, pid):
590
+ for hwnd in get_excel_hwnds():
591
+ app = App(xl=hwnd)
592
+ if app.pid == pid:
593
+ return app
594
+ raise KeyError("Could not find an Excel instance with this PID.")
595
+
596
+
597
+ class App(base_classes.App):
598
+ def __init__(self, spec=None, add_book=True, xl=None, visible=None):
599
+ # visible is only required on mac
600
+ pythoncom.CoInitialize()
601
+ if spec is not None:
602
+ warn("spec is ignored on Windows.")
603
+ if xl is None:
604
+ # new instance
605
+ self._xl = COMRetryObjectWrapper(DispatchEx("Excel.Application"))
606
+ if add_book:
607
+ self._xl.Workbooks.Add()
608
+ self._hwnd = None
609
+ elif isinstance(xl, int):
610
+ self._xl = None
611
+ self._hwnd = xl
612
+ else:
613
+ self._xl = xl
614
+ self._hwnd = None
615
+ self._pid = self.pid
616
+
617
+ @property
618
+ def xl(self):
619
+ if self._xl is None:
620
+ self._xl = get_xl_app_from_hwnd(self._hwnd)
621
+ return self._xl
622
+
623
+ @xl.setter
624
+ def xl(self, value):
625
+ self._xl = value
626
+
627
+ api = xl
628
+
629
+ @property
630
+ def engine(self):
631
+ return engine
632
+
633
+ @property
634
+ def selection(self):
635
+ try:
636
+ _ = (
637
+ self.xl.Selection.Address
638
+ ) # Force exception outside of the retry wrapper e.g., if chart is selected
639
+ return Range(xl=self.xl.Selection)
640
+ except pywintypes.com_error:
641
+ return None
642
+
643
+ def activate(self, steal_focus=False):
644
+ # makes the Excel instance the foreground Excel instance,
645
+ # but not the foreground desktop app if the current foreground
646
+ # app isn't already an Excel instance
647
+ hwnd = windll.user32.GetForegroundWindow()
648
+ if steal_focus or is_hwnd_xl_app(hwnd):
649
+ windll.user32.SetForegroundWindow(self.xl.Hwnd)
650
+ else:
651
+ windll.user32.SetWindowPos(self.xl.Hwnd, hwnd, 0, 0, 0, 0, 0x1 | 0x2 | 0x10)
652
+
653
+ @property
654
+ def visible(self):
655
+ return self.xl.Visible
656
+
657
+ @visible.setter
658
+ def visible(self, visible):
659
+ self.xl.Visible = visible
660
+
661
+ def quit(self):
662
+ self.xl.DisplayAlerts = False
663
+ self.xl.Quit()
664
+ self.xl = None
665
+ try:
666
+ Apps.cleanup()
667
+ except: # noqa: E722
668
+ pass
669
+
670
+ def kill(self):
671
+ PROCESS_TERMINATE = 1
672
+ handle = win32api.OpenProcess(PROCESS_TERMINATE, False, self._pid)
673
+ win32api.TerminateProcess(handle, -1)
674
+ win32api.CloseHandle(handle)
675
+ try:
676
+ Apps.cleanup()
677
+ except: # noqa: E722
678
+ pass
679
+
680
+ @property
681
+ def screen_updating(self):
682
+ return self.xl.ScreenUpdating
683
+
684
+ @screen_updating.setter
685
+ def screen_updating(self, value):
686
+ self.xl.ScreenUpdating = value
687
+
688
+ @property
689
+ def display_alerts(self):
690
+ return self.xl.DisplayAlerts
691
+
692
+ @display_alerts.setter
693
+ def display_alerts(self, value):
694
+ self.xl.DisplayAlerts = value
695
+
696
+ @property
697
+ def enable_events(self):
698
+ return self.xl.EnableEvents
699
+
700
+ @enable_events.setter
701
+ def enable_events(self, value):
702
+ self.xl.EnableEvents = value
703
+
704
+ @property
705
+ def interactive(self):
706
+ return self.xl.Interactive
707
+
708
+ @interactive.setter
709
+ def interactive(self, value):
710
+ self.xl.Interactive = value
711
+
712
+ @property
713
+ def startup_path(self):
714
+ return self.xl.StartupPath
715
+
716
+ @property
717
+ def calculation(self):
718
+ return calculation_i2s[self.xl.Calculation]
719
+
720
+ @calculation.setter
721
+ def calculation(self, value):
722
+ self.xl.Calculation = calculation_s2i[value]
723
+
724
+ def calculate(self):
725
+ self.xl.Calculate()
726
+
727
+ @property
728
+ def version(self):
729
+ return self.xl.Version
730
+
731
+ @property
732
+ def books(self):
733
+ return Books(xl=self.xl.Workbooks, app=self)
734
+
735
+ @property
736
+ def hwnd(self):
737
+ if self._hwnd is None:
738
+ self._hwnd = self._xl.Hwnd
739
+ return self._hwnd
740
+
741
+ @property
742
+ def path(self):
743
+ return self.xl.Path
744
+
745
+ @property
746
+ def pid(self):
747
+ return win32process.GetWindowThreadProcessId(self.hwnd)[1]
748
+
749
+ def run(self, macro, args):
750
+ return self.xl.Run(macro, *args)
751
+
752
+ @property
753
+ def status_bar(self):
754
+ return self.xl.StatusBar
755
+
756
+ @status_bar.setter
757
+ def status_bar(self, value):
758
+ self.xl.StatusBar = value
759
+
760
+ @property
761
+ def cut_copy_mode(self):
762
+ modes = {2: "cut", 1: "copy"}
763
+ return modes.get(self.xl.CutCopyMode)
764
+
765
+ @cut_copy_mode.setter
766
+ def cut_copy_mode(self, value):
767
+ self.xl.CutCopyMode = value
768
+
769
+ def alert(self, prompt, title, buttons, mode, callback):
770
+ buttons_dict = {
771
+ None: win32con.MB_OK,
772
+ "ok": win32con.MB_OK,
773
+ "ok_cancel": win32con.MB_OKCANCEL,
774
+ "yes_no": win32con.MB_YESNO,
775
+ "yes_no_cancel": win32con.MB_YESNOCANCEL,
776
+ }
777
+ modes = {
778
+ "info": win32con.MB_ICONINFORMATION,
779
+ "critical": win32con.MB_ICONWARNING,
780
+ }
781
+ style = buttons_dict[buttons]
782
+ if mode:
783
+ style += modes[mode]
784
+ rv = win32api.MessageBox(
785
+ self.hwnd,
786
+ "" if prompt is None else prompt,
787
+ "" if title is None else title,
788
+ style,
789
+ )
790
+ return_values = {1: "ok", 2: "cancel", 6: "yes", 7: "no"}
791
+ return return_values[rv]
792
+
793
+
794
+ class Books(base_classes.Books):
795
+ def __init__(self, xl, app):
796
+ self.xl = xl
797
+ self.app = app
798
+
799
+ @property
800
+ def api(self):
801
+ return self.xl
802
+
803
+ @property
804
+ def active(self):
805
+ return Book(self.xl.Application.ActiveWorkbook)
806
+
807
+ def __call__(self, name_or_index):
808
+ try:
809
+ return Book(xl=self.xl(name_or_index))
810
+ except pywintypes.com_error:
811
+ raise KeyError(name_or_index)
812
+
813
+ def __len__(self):
814
+ return self.xl.Count
815
+
816
+ def add(self):
817
+ return Book(xl=self.xl.Add())
818
+
819
+ def open(
820
+ self,
821
+ fullname,
822
+ update_links=None,
823
+ read_only=None,
824
+ format=None,
825
+ password=None,
826
+ write_res_password=None,
827
+ ignore_read_only_recommended=None,
828
+ origin=None,
829
+ delimiter=None,
830
+ editable=None,
831
+ notify=None,
832
+ converter=None,
833
+ add_to_mru=None,
834
+ local=None,
835
+ corrupt_load=None,
836
+ ):
837
+ # update_links: According to VBA docs, only constants 0 and 3 are supported
838
+ if update_links:
839
+ update_links = UpdateLinks.xlUpdateLinksAlways
840
+ # Workbooks.Open params are position only on pywin32
841
+ return Book(
842
+ xl=self.xl.Open(
843
+ fullname,
844
+ update_links,
845
+ read_only,
846
+ format,
847
+ password,
848
+ write_res_password,
849
+ ignore_read_only_recommended,
850
+ origin,
851
+ delimiter,
852
+ editable,
853
+ notify,
854
+ converter,
855
+ add_to_mru,
856
+ local,
857
+ corrupt_load,
858
+ )
859
+ )
860
+
861
+ def __iter__(self):
862
+ for xl in self.xl:
863
+ yield Book(xl=xl)
864
+
865
+
866
+ class Book(base_classes.Book):
867
+ def __init__(self, xl):
868
+ self.xl = xl
869
+
870
+ @property
871
+ def api(self):
872
+ return self.xl
873
+
874
+ def json(self):
875
+ raise NotImplementedError()
876
+
877
+ @property
878
+ def name(self):
879
+ return self.xl.Name
880
+
881
+ @property
882
+ def sheets(self):
883
+ return Sheets(xl=self.xl.Worksheets)
884
+
885
+ @property
886
+ def app(self):
887
+ return App(xl=self.xl.Application)
888
+
889
+ def close(self):
890
+ self.xl.Close(SaveChanges=False)
891
+
892
+ def save(self, path=None, password=None):
893
+ saved_path = self.xl.Path
894
+ source_ext = os.path.splitext(self.name)[1] if saved_path else None
895
+ target_ext = os.path.splitext(path)[1] if path else ".xlsx"
896
+ if saved_path and source_ext == target_ext:
897
+ file_format = self.xl.FileFormat
898
+ else:
899
+ ext_to_file_format = {
900
+ ".xlsx": FileFormat.xlOpenXMLWorkbook,
901
+ ".xlsm": FileFormat.xlOpenXMLWorkbookMacroEnabled,
902
+ ".xlsb": FileFormat.xlExcel12,
903
+ ".xltm": FileFormat.xlOpenXMLTemplateMacroEnabled,
904
+ ".xltx": FileFormat.xlOpenXMLTemplateMacroEnabled,
905
+ ".xlam": FileFormat.xlOpenXMLAddIn,
906
+ ".xls": FileFormat.xlWorkbookNormal,
907
+ ".xlt": FileFormat.xlTemplate,
908
+ ".xla": FileFormat.xlAddIn,
909
+ ".html": FileFormat.xlHtml,
910
+ }
911
+ file_format = ext_to_file_format[target_ext]
912
+ if (saved_path != "") and (path is None):
913
+ # Previously saved: Save under existing name
914
+ self.xl.Save()
915
+ elif (
916
+ (saved_path != "") and (path is not None) and (os.path.split(path)[0] == "")
917
+ ):
918
+ # Save existing book under new name in cwd if no path has been provided
919
+ path = os.path.join(os.getcwd(), path)
920
+ self.xl.SaveAs(
921
+ os.path.realpath(path), FileFormat=file_format, Password=password
922
+ )
923
+ elif (saved_path == "") and (path is None):
924
+ # Previously unsaved: Save under current name in current working directory
925
+ path = os.path.join(os.getcwd(), self.xl.Name + ".xlsx")
926
+ alerts_state = self.xl.Application.DisplayAlerts
927
+ self.xl.Application.DisplayAlerts = False
928
+ self.xl.SaveAs(
929
+ os.path.realpath(path), FileFormat=file_format, Password=password
930
+ )
931
+ self.xl.Application.DisplayAlerts = alerts_state
932
+ elif path:
933
+ # Save under new name/location
934
+ alerts_state = self.xl.Application.DisplayAlerts
935
+ self.xl.Application.DisplayAlerts = False
936
+ self.xl.SaveAs(
937
+ os.path.realpath(path), FileFormat=file_format, Password=password
938
+ )
939
+ self.xl.Application.DisplayAlerts = alerts_state
940
+
941
+ @property
942
+ def fullname(self):
943
+ if "://" in self.xl.FullName:
944
+ config = read_config_sheet(xlwings.Book(impl=self))
945
+ return fullname_url_to_local_path(
946
+ url=self.xl.FullName,
947
+ sheet_onedrive_consumer_config=config.get("ONEDRIVE_CONSUMER_WIN"),
948
+ sheet_onedrive_commercial_config=config.get("ONEDRIVE_COMMERCIAL_WIN"),
949
+ sheet_sharepoint_config=config.get("SHAREPOINT_WIN"),
950
+ )
951
+ else:
952
+ return self.xl.FullName
953
+
954
+ @property
955
+ def names(self):
956
+ return Names(xl=self.xl.Names)
957
+
958
+ def activate(self):
959
+ self.xl.Activate()
960
+
961
+ def to_pdf(self, path, quality):
962
+ self.xl.ExportAsFixedFormat(
963
+ Type=FixedFormatType.xlTypePDF,
964
+ Filename=path,
965
+ Quality=quality_types[quality],
966
+ IncludeDocProperties=True,
967
+ IgnorePrintAreas=False,
968
+ OpenAfterPublish=False,
969
+ )
970
+
971
+
972
+ class Sheets(base_classes.Sheets):
973
+ def __init__(self, xl):
974
+ self.xl = xl
975
+
976
+ @property
977
+ def api(self):
978
+ return self.xl
979
+
980
+ @property
981
+ def active(self):
982
+ return Sheet(self.xl.Parent.ActiveSheet)
983
+
984
+ def __call__(self, name_or_index):
985
+ return Sheet(xl=self.xl(name_or_index))
986
+
987
+ def __len__(self):
988
+ return self.xl.Count
989
+
990
+ def __iter__(self):
991
+ for xl in self.xl:
992
+ yield Sheet(xl=xl)
993
+
994
+ def add(self, before=None, after=None, name=None):
995
+ if before:
996
+ sheet = Sheet(xl=self.xl.Add(Before=before.xl))
997
+ if name is not None:
998
+ sheet.name = name
999
+ return sheet
1000
+ elif after:
1001
+ # Hack, since "After" is broken in certain environments
1002
+ # see: http://code.activestate.com/lists/python-win32/11554/
1003
+ count = self.xl.Count
1004
+ new_sheet_index = after.xl.Index + 1
1005
+ if new_sheet_index > count:
1006
+ xl_sheet = self.xl.Add(Before=after.xl)
1007
+ self.xl(self.xl.Count).Move(Before=self.xl(self.xl.Count - 1))
1008
+ self.xl(self.xl.Count).Activate()
1009
+ else:
1010
+ xl_sheet = self.xl.Add(Before=self.xl(after.xl.Index + 1))
1011
+ sheet = Sheet(xl=xl_sheet)
1012
+ if name is not None:
1013
+ sheet.name = name
1014
+ return sheet
1015
+ else:
1016
+ sheet = Sheet(xl=self.xl.Add())
1017
+ if name is not None:
1018
+ sheet.name = name
1019
+ return sheet
1020
+
1021
+
1022
+ class Sheet(base_classes.Sheet):
1023
+ def __init__(self, xl):
1024
+ self.xl = xl
1025
+
1026
+ @property
1027
+ def api(self):
1028
+ return self.xl
1029
+
1030
+ @property
1031
+ def name(self):
1032
+ return self.xl.Name
1033
+
1034
+ @name.setter
1035
+ def name(self, value):
1036
+ self.xl.Name = value
1037
+
1038
+ @property
1039
+ def names(self):
1040
+ return Names(xl=self.xl.Names)
1041
+
1042
+ @property
1043
+ def book(self):
1044
+ return Book(xl=self.xl.Parent)
1045
+
1046
+ @property
1047
+ def index(self):
1048
+ return self.xl.Index
1049
+
1050
+ def range(self, arg1, arg2=None):
1051
+ if isinstance(arg1, Range):
1052
+ xl1 = arg1.xl
1053
+ elif isinstance(arg1, tuple):
1054
+ if len(arg1) == 4:
1055
+ row, col, nrows, ncols = arg1
1056
+ return Range(xl=(self.xl, row, col, nrows, ncols))
1057
+ if 0 in arg1:
1058
+ raise IndexError(
1059
+ "Attempted to access 0-based Range. "
1060
+ "xlwings/Excel Ranges are 1-based."
1061
+ )
1062
+ xl1 = self.xl.Cells(arg1[0], arg1[1])
1063
+ elif isinstance(arg1, numbers.Number) and isinstance(arg2, numbers.Number):
1064
+ xl1 = self.xl.Cells(arg1, arg2)
1065
+ arg2 = None
1066
+ else:
1067
+ xl1 = self.xl.Range(arg1)
1068
+
1069
+ if arg2 is None:
1070
+ return Range(xl=xl1)
1071
+
1072
+ if isinstance(arg2, Range):
1073
+ xl2 = arg2.xl
1074
+ elif isinstance(arg2, tuple):
1075
+ if 0 in arg2:
1076
+ raise IndexError(
1077
+ "Attempted to access 0-based Range. "
1078
+ "xlwings/Excel Ranges are 1-based."
1079
+ )
1080
+ xl2 = self.xl.Cells(arg2[0], arg2[1])
1081
+ else:
1082
+ xl2 = self.xl.Range(arg2)
1083
+
1084
+ return Range(xl=self.xl.Range(xl1, xl2))
1085
+
1086
+ @property
1087
+ def cells(self):
1088
+ return Range(xl=self.xl.Cells)
1089
+
1090
+ def activate(self):
1091
+ return self.xl.Activate()
1092
+
1093
+ def select(self):
1094
+ return self.xl.Select()
1095
+
1096
+ def clear_contents(self):
1097
+ self.xl.Cells.ClearContents()
1098
+
1099
+ def clear_formats(self):
1100
+ self.xl.Cells.ClearFormats()
1101
+
1102
+ def clear(self):
1103
+ self.xl.Cells.Clear()
1104
+
1105
+ def autofit(self, axis=None):
1106
+ if axis == "rows" or axis == "r":
1107
+ self.xl.Rows.AutoFit()
1108
+ elif axis == "columns" or axis == "c":
1109
+ self.xl.Columns.AutoFit()
1110
+ elif axis is None:
1111
+ self.xl.Rows.AutoFit()
1112
+ self.xl.Columns.AutoFit()
1113
+
1114
+ def delete(self):
1115
+ app = self.xl.Parent.Application
1116
+ alerts_state = app.DisplayAlerts
1117
+ app.DisplayAlerts = False
1118
+ self.xl.Delete()
1119
+ app.DisplayAlerts = alerts_state
1120
+
1121
+ def copy(self, before, after):
1122
+ if before:
1123
+ before = before.xl
1124
+ if after:
1125
+ after = after.xl
1126
+ self.xl.Copy(Before=before, After=after)
1127
+
1128
+ @property
1129
+ def charts(self):
1130
+ return Charts(xl=self.xl.ChartObjects())
1131
+
1132
+ @property
1133
+ def shapes(self):
1134
+ return Shapes(xl=self.xl.Shapes)
1135
+
1136
+ @property
1137
+ def tables(self):
1138
+ return Tables(xl=self.xl.ListObjects)
1139
+
1140
+ @property
1141
+ def pictures(self):
1142
+ return Pictures(xl=self.xl.Pictures())
1143
+
1144
+ @property
1145
+ def used_range(self):
1146
+ return Range(xl=self.xl.UsedRange)
1147
+
1148
+ @property
1149
+ def visible(self):
1150
+ return self.xl.Visible
1151
+
1152
+ @visible.setter
1153
+ def visible(self, value):
1154
+ self.xl.Visible = value
1155
+
1156
+ @property
1157
+ def page_setup(self):
1158
+ return PageSetup(self.xl.PageSetup)
1159
+
1160
+ def to_html(self, path):
1161
+ if not Path(path).is_absolute():
1162
+ path = Path(".").resolve() / path
1163
+ source_cell2 = self.used_range.address.split(":")
1164
+ if len(source_cell2) == 2:
1165
+ source = f"A1:{source_cell2[1]}"
1166
+ else:
1167
+ source = f"A1:{source_cell2[0]}"
1168
+ self.book.xl.PublishObjects.Add(
1169
+ SourceType=SourceType.xlSourceRange,
1170
+ Filename=path,
1171
+ Sheet=self.name,
1172
+ Source=source,
1173
+ HtmlType=HtmlType.xlHtmlStatic,
1174
+ ).Publish(True)
1175
+ html_file = Path(path)
1176
+ content = html_file.read_text()
1177
+ html_file.write_text(
1178
+ content.replace(
1179
+ "align=center x:publishsource=", "align=left x:publishsource="
1180
+ )
1181
+ )
1182
+
1183
+
1184
+ class Range(base_classes.Range):
1185
+ def __init__(self, xl):
1186
+ if isinstance(xl, tuple):
1187
+ self._coords = xl
1188
+ self._xl = missing
1189
+ else:
1190
+ self._coords = missing
1191
+ self._xl = xl
1192
+
1193
+ @property
1194
+ def xl(self):
1195
+ if self._xl is missing:
1196
+ xl_sheet, row, col, nrows, ncols = self._coords
1197
+ if nrows and ncols:
1198
+ self._xl = xl_sheet.Range(
1199
+ xl_sheet.Cells(row, col),
1200
+ xl_sheet.Cells(row + nrows - 1, col + ncols - 1),
1201
+ )
1202
+ else:
1203
+ self._xl = None
1204
+ return self._xl
1205
+
1206
+ @property
1207
+ def coords(self):
1208
+ if self._coords is missing:
1209
+ self._coords = (
1210
+ self.xl.Worksheet,
1211
+ self.xl.Row,
1212
+ self.xl.Column,
1213
+ self.xl.Rows.Count,
1214
+ self.xl.Columns.Count,
1215
+ )
1216
+ return self._coords
1217
+
1218
+ @property
1219
+ def api(self):
1220
+ return self.xl
1221
+
1222
+ @property
1223
+ def sheet(self):
1224
+ return Sheet(xl=self.coords[0])
1225
+
1226
+ def __len__(self):
1227
+ return (self.xl and self.xl.Count) or 0
1228
+
1229
+ @property
1230
+ def row(self):
1231
+ return self.coords[1]
1232
+
1233
+ @property
1234
+ def column(self):
1235
+ return self.coords[2]
1236
+
1237
+ @property
1238
+ def shape(self):
1239
+ return self.coords[3], self.coords[4]
1240
+
1241
+ @property
1242
+ def raw_value(self):
1243
+ if self.xl is not None:
1244
+ return self.xl.Value
1245
+ else:
1246
+ return None
1247
+
1248
+ @raw_value.setter
1249
+ def raw_value(self, data):
1250
+ if self.xl is not None:
1251
+ self.xl.Value = data
1252
+
1253
+ def clear_contents(self):
1254
+ if self.xl is not None:
1255
+ self.xl.ClearContents()
1256
+
1257
+ def clear_formats(self):
1258
+ self.xl.ClearFormats()
1259
+
1260
+ def clear(self):
1261
+ if self.xl is not None:
1262
+ self.xl.Clear()
1263
+
1264
+ @property
1265
+ def formula(self):
1266
+ if self.xl is not None:
1267
+ return self.xl.Formula
1268
+ else:
1269
+ return None
1270
+
1271
+ @formula.setter
1272
+ def formula(self, value):
1273
+ if self.xl is not None:
1274
+ self.xl.Formula = value
1275
+
1276
+ @property
1277
+ def formula2(self):
1278
+ if self.xl is not None:
1279
+ return self.xl.Formula2
1280
+ else:
1281
+ return None
1282
+
1283
+ @formula2.setter
1284
+ def formula2(self, value):
1285
+ if self.xl is not None:
1286
+ self.xl.Formula2 = value
1287
+
1288
+ def end(self, direction):
1289
+ direction = directions_s2i.get(direction, direction)
1290
+ return Range(xl=self.xl.End(direction))
1291
+
1292
+ @property
1293
+ def formula_array(self):
1294
+ if self.xl is not None:
1295
+ return self.xl.FormulaArray
1296
+ else:
1297
+ return None
1298
+
1299
+ @formula_array.setter
1300
+ def formula_array(self, value):
1301
+ if self.xl is not None:
1302
+ self.xl.FormulaArray = value
1303
+
1304
+ @property
1305
+ def font(self):
1306
+ return Font(self, self.xl.Font)
1307
+
1308
+ @property
1309
+ def column_width(self):
1310
+ if self.xl is not None:
1311
+ return self.xl.ColumnWidth
1312
+ else:
1313
+ return 0
1314
+
1315
+ @column_width.setter
1316
+ def column_width(self, value):
1317
+ if self.xl is not None:
1318
+ self.xl.ColumnWidth = value
1319
+
1320
+ @property
1321
+ def row_height(self):
1322
+ if self.xl is not None:
1323
+ return self.xl.RowHeight
1324
+ else:
1325
+ return 0
1326
+
1327
+ @row_height.setter
1328
+ def row_height(self, value):
1329
+ if self.xl is not None:
1330
+ self.xl.RowHeight = value
1331
+
1332
+ @property
1333
+ def width(self):
1334
+ if self.xl is not None:
1335
+ return self.xl.Width
1336
+ else:
1337
+ return 0
1338
+
1339
+ @property
1340
+ def height(self):
1341
+ if self.xl is not None:
1342
+ return self.xl.Height
1343
+ else:
1344
+ return 0
1345
+
1346
+ @property
1347
+ def left(self):
1348
+ if self.xl is not None:
1349
+ return self.xl.Left
1350
+ else:
1351
+ return 0
1352
+
1353
+ @property
1354
+ def top(self):
1355
+ if self.xl is not None:
1356
+ return self.xl.Top
1357
+ else:
1358
+ return 0
1359
+
1360
+ @property
1361
+ def number_format(self):
1362
+ if self.xl is not None:
1363
+ return self.xl.NumberFormat
1364
+ else:
1365
+ return ""
1366
+
1367
+ @number_format.setter
1368
+ def number_format(self, value):
1369
+ if self.xl is not None:
1370
+ self.xl.NumberFormat = value
1371
+
1372
+ def get_address(self, row_absolute, col_absolute, external):
1373
+ if self.xl is not None:
1374
+ return self.xl.GetAddress(row_absolute, col_absolute, 1, external)
1375
+ else:
1376
+ raise NotImplementedError()
1377
+
1378
+ @property
1379
+ def address(self):
1380
+ if self.xl is not None:
1381
+ return self.xl.Address
1382
+ else:
1383
+ _, row, col, nrows, ncols = self.coords
1384
+ return "$%s$%s{%sx%s}" % (col_name(col), str(row), nrows, ncols)
1385
+
1386
+ @property
1387
+ def current_region(self):
1388
+ if self.xl is not None:
1389
+ return Range(xl=self.xl.CurrentRegion)
1390
+ else:
1391
+ return self
1392
+
1393
+ def autofit(self, axis=None):
1394
+ if self.xl is not None:
1395
+ if axis == "rows" or axis == "r":
1396
+ self.xl.Rows.AutoFit()
1397
+ elif axis == "columns" or axis == "c":
1398
+ self.xl.Columns.AutoFit()
1399
+ elif axis is None:
1400
+ self.xl.Columns.AutoFit()
1401
+ self.xl.Rows.AutoFit()
1402
+
1403
+ def insert(self, shift=None, copy_origin=None):
1404
+ shifts = {
1405
+ "down": InsertShiftDirection.xlShiftDown,
1406
+ "right": InsertShiftDirection.xlShiftToRight,
1407
+ None: None,
1408
+ }
1409
+ copy_origins = {
1410
+ "format_from_left_or_above": InsertFormatOrigin.xlFormatFromLeftOrAbove,
1411
+ "format_from_right_or_below": InsertFormatOrigin.xlFormatFromRightOrBelow,
1412
+ }
1413
+ self.xl.Insert(Shift=shifts[shift], CopyOrigin=copy_origins[copy_origin])
1414
+
1415
+ def delete(self, shift=None):
1416
+ shifts = {
1417
+ "up": DeleteShiftDirection.xlShiftUp,
1418
+ "left": DeleteShiftDirection.xlShiftToLeft,
1419
+ None: None,
1420
+ }
1421
+ self.xl.Delete(Shift=shifts[shift])
1422
+
1423
+ def copy(self, destination=None):
1424
+ self.xl.Copy(Destination=destination.api if destination else None)
1425
+
1426
+ def paste(self, paste=None, operation=None, skip_blanks=False, transpose=False):
1427
+ pastes = {
1428
+ "all": -4104,
1429
+ None: -4104,
1430
+ "all_except_borders": 7,
1431
+ "all_merging_conditional_formats": 14,
1432
+ "all_using_source_theme": 13,
1433
+ "column_widths": 8,
1434
+ "comments": -4144,
1435
+ "formats": -4122,
1436
+ "formulas": -4123,
1437
+ "formulas_and_number_formats": 11,
1438
+ "validation": 6,
1439
+ "values": -4163,
1440
+ "values_and_number_formats": 12,
1441
+ }
1442
+
1443
+ operations = {
1444
+ "add": 2,
1445
+ "divide": 5,
1446
+ "multiply": 4,
1447
+ None: -4142,
1448
+ "subtract": 3,
1449
+ }
1450
+
1451
+ self.xl.PasteSpecial(
1452
+ Paste=pastes[paste],
1453
+ Operation=operations[operation],
1454
+ SkipBlanks=skip_blanks,
1455
+ Transpose=transpose,
1456
+ )
1457
+
1458
+ @property
1459
+ def hyperlink(self):
1460
+ if self.xl is not None:
1461
+ try:
1462
+ return self.xl.Hyperlinks(1).Address
1463
+ except pywintypes.com_error:
1464
+ raise Exception("The cell doesn't seem to contain a hyperlink!")
1465
+ else:
1466
+ return ""
1467
+
1468
+ def add_hyperlink(self, address, text_to_display, screen_tip):
1469
+ if self.xl is not None:
1470
+ # Another one of these pywin32 bugs that only materialize under certain
1471
+ # circumstances: https://stackoverflow.com/questions/
1472
+ # 6284227/hyperlink-will-not-show-display-proper-text
1473
+ link = self.xl.Hyperlinks.Add(Anchor=self.xl, Address=address)
1474
+ link.TextToDisplay = text_to_display
1475
+ link.ScreenTip = screen_tip
1476
+
1477
+ @property
1478
+ def color(self):
1479
+ if self.xl is not None:
1480
+ if self.xl.Interior.ColorIndex == ColorIndex.xlColorIndexNone:
1481
+ return None
1482
+ else:
1483
+ return int_to_rgb(self.xl.Interior.Color)
1484
+ else:
1485
+ return None
1486
+
1487
+ @color.setter
1488
+ def color(self, color_or_rgb):
1489
+ if isinstance(color_or_rgb, str):
1490
+ color_or_rgb = hex_to_rgb(color_or_rgb)
1491
+ if self.xl is not None:
1492
+ if color_or_rgb is None:
1493
+ self.xl.Interior.ColorIndex = ColorIndex.xlColorIndexNone
1494
+ elif isinstance(color_or_rgb, int):
1495
+ self.xl.Interior.Color = color_or_rgb
1496
+ else:
1497
+ self.xl.Interior.Color = rgb_to_int(color_or_rgb)
1498
+
1499
+ @property
1500
+ def name(self):
1501
+ if self.xl is not None:
1502
+ try:
1503
+ name = Name(xl=self.xl.Name)
1504
+ except pywintypes.com_error:
1505
+ name = None
1506
+ return name
1507
+ else:
1508
+ return None
1509
+
1510
+ @property
1511
+ def has_array(self):
1512
+ if self.xl is not None:
1513
+ try:
1514
+ return self.xl.HasArray
1515
+ except pywintypes.com_error:
1516
+ return False
1517
+ else:
1518
+ return False
1519
+
1520
+ @name.setter
1521
+ def name(self, value):
1522
+ if self.xl is not None:
1523
+ self.xl.Name = value
1524
+
1525
+ def __call__(self, *args):
1526
+ if self.xl is not None:
1527
+ if len(args) == 0:
1528
+ raise ValueError("Invalid arguments")
1529
+ return Range(xl=self.xl(*args))
1530
+ else:
1531
+ raise NotImplementedError()
1532
+
1533
+ @property
1534
+ def rows(self):
1535
+ return Range(xl=self.xl.Rows)
1536
+
1537
+ @property
1538
+ def columns(self):
1539
+ return Range(xl=self.xl.Columns)
1540
+
1541
+ def select(self):
1542
+ return self.xl.Select()
1543
+
1544
+ @property
1545
+ def merge_area(self):
1546
+ return Range(xl=self.xl.MergeArea)
1547
+
1548
+ @property
1549
+ def merge_cells(self):
1550
+ return self.xl.MergeCells
1551
+
1552
+ def merge(self, across):
1553
+ self.xl.Merge(across)
1554
+
1555
+ def unmerge(self):
1556
+ self.xl.UnMerge()
1557
+
1558
+ @property
1559
+ def table(self):
1560
+ if self.xl.ListObject:
1561
+ return Table(self.xl.ListObject)
1562
+
1563
+ @property
1564
+ def characters(self):
1565
+ return Characters(parent=self, xl=self.xl.GetCharacters)
1566
+
1567
+ @property
1568
+ def wrap_text(self):
1569
+ return self.xl.WrapText
1570
+
1571
+ @wrap_text.setter
1572
+ def wrap_text(self, value):
1573
+ self.xl.WrapText = value
1574
+
1575
+ @property
1576
+ def note(self):
1577
+ return Note(xl=self.xl.Comment) if self.xl.Comment else None
1578
+
1579
+ def copy_picture(self, appearance, format):
1580
+ _appearance = {"screen": 1, "printer": 2}
1581
+ _format = {"picture": -4147, "bitmap": 2}
1582
+ self.xl.CopyPicture(Appearance=_appearance[appearance], Format=_format[format])
1583
+
1584
+ def to_png(self, path):
1585
+ max_retries = 10
1586
+ for retry in range(max_retries):
1587
+ # https://stackoverflow.com/questions/
1588
+ # 24740062/copypicture-method-of-range-class-failed-sometimes
1589
+ try:
1590
+ # appearance="printer" fails here, not sure why
1591
+ self.copy_picture(appearance="screen", format="bitmap")
1592
+ im = ImageGrab.grabclipboard()
1593
+ im.save(path)
1594
+ break
1595
+ except (pywintypes.com_error, AttributeError):
1596
+ if retry == max_retries - 1:
1597
+ raise
1598
+
1599
+ def to_pdf(self, path, quality):
1600
+ self.xl.ExportAsFixedFormat(
1601
+ Type=FixedFormatType.xlTypePDF,
1602
+ Filename=path,
1603
+ Quality=quality_types[quality],
1604
+ IncludeDocProperties=True,
1605
+ IgnorePrintAreas=False,
1606
+ OpenAfterPublish=False,
1607
+ )
1608
+
1609
+ def autofill(self, destination, type_):
1610
+ types = {
1611
+ "fill_copy": constants.AutoFillType.xlFillCopy,
1612
+ "fill_days": constants.AutoFillType.xlFillDays,
1613
+ "fill_default": constants.AutoFillType.xlFillDefault,
1614
+ "fill_formats": constants.AutoFillType.xlFillFormats,
1615
+ "fill_months": constants.AutoFillType.xlFillMonths,
1616
+ "fill_series": constants.AutoFillType.xlFillSeries,
1617
+ "fill_values": constants.AutoFillType.xlFillValues,
1618
+ "fill_weekdays": constants.AutoFillType.xlFillWeekdays,
1619
+ "fill_years": constants.AutoFillType.xlFillYears,
1620
+ "growth_trend": constants.AutoFillType.xlGrowthTrend,
1621
+ "linear_trend": constants.AutoFillType.xlLinearTrend,
1622
+ "flash_fill": constants.AutoFillType.xlFlashFill,
1623
+ }
1624
+ self.xl.AutoFill(Destination=destination.api, Type=types[type_])
1625
+
1626
+
1627
+ class Shape(base_classes.Shape):
1628
+ def __init__(self, xl):
1629
+ self.xl = xl
1630
+
1631
+ @property
1632
+ def api(self):
1633
+ return self.xl
1634
+
1635
+ @property
1636
+ def name(self):
1637
+ return self.xl.Name
1638
+
1639
+ @property
1640
+ def parent(self):
1641
+ return Sheet(xl=self.xl.Parent)
1642
+
1643
+ @property
1644
+ def type(self):
1645
+ return shape_types_i2s[self.xl.Type]
1646
+
1647
+ @property
1648
+ def left(self):
1649
+ return self.xl.Left
1650
+
1651
+ @left.setter
1652
+ def left(self, value):
1653
+ self.xl.Left = value
1654
+
1655
+ @property
1656
+ def top(self):
1657
+ return self.xl.Top
1658
+
1659
+ @top.setter
1660
+ def top(self, value):
1661
+ self.xl.Top = value
1662
+
1663
+ @property
1664
+ def width(self):
1665
+ return self.xl.Width
1666
+
1667
+ @width.setter
1668
+ def width(self, value):
1669
+ self.xl.Width = value
1670
+
1671
+ @property
1672
+ def height(self):
1673
+ return self.xl.Height
1674
+
1675
+ @height.setter
1676
+ def height(self, value):
1677
+ self.xl.Height = value
1678
+
1679
+ def delete(self):
1680
+ self.xl.Delete()
1681
+
1682
+ @name.setter
1683
+ def name(self, value):
1684
+ self.xl.Name = value
1685
+
1686
+ @property
1687
+ def index(self):
1688
+ return self.xl.Index
1689
+
1690
+ def activate(self):
1691
+ self.xl.Activate()
1692
+
1693
+ def scale_height(self, factor, relative_to_original_size, scale):
1694
+ self.xl.ScaleHeight(
1695
+ Scale=scaling[scale],
1696
+ RelativeToOriginalSize=relative_to_original_size,
1697
+ Factor=factor,
1698
+ )
1699
+
1700
+ def scale_width(self, factor, relative_to_original_size, scale):
1701
+ self.xl.ScaleWidth(
1702
+ Scale=scaling[scale],
1703
+ RelativeToOriginalSize=relative_to_original_size,
1704
+ Factor=factor,
1705
+ )
1706
+
1707
+ @property
1708
+ def text(self):
1709
+ if self.xl.TextFrame2.HasText:
1710
+ return self.xl.TextFrame2.TextRange.Text
1711
+
1712
+ @text.setter
1713
+ def text(self, value):
1714
+ self.xl.TextFrame2.TextRange.Text = value
1715
+
1716
+ @property
1717
+ def font(self):
1718
+ return Font(self, self.xl.TextFrame2.TextRange.Font)
1719
+
1720
+ @property
1721
+ def characters(self):
1722
+ return Characters(parent=self, xl=self.xl.TextFrame2.TextRange.GetCharacters)
1723
+
1724
+
1725
+ class Font(base_classes.Font):
1726
+ def __init__(self, parent, xl):
1727
+ self.parent = parent
1728
+ self.xl = xl
1729
+
1730
+ @property
1731
+ def api(self):
1732
+ return self.xl
1733
+
1734
+ @property
1735
+ def bold(self):
1736
+ if isinstance(self.parent, Range):
1737
+ return self.xl.Bold
1738
+ elif isinstance(self.parent, Shape):
1739
+ return True if self.xl.Bold == -1 else False
1740
+ elif isinstance(self.parent.parent, Range):
1741
+ return self.xl.Bold
1742
+ elif isinstance(self.parent.parent, Shape):
1743
+ return True if self.xl.Bold == -1 else False
1744
+ elif isinstance(self.parent.parent.parent, Range):
1745
+ return self.xl.Bold
1746
+ elif isinstance(self.parent.parent.parent, Shape):
1747
+ return True if self.xl.Bold == -1 else False
1748
+
1749
+ @bold.setter
1750
+ def bold(self, value):
1751
+ self.xl.Bold = value
1752
+
1753
+ @property
1754
+ def italic(self):
1755
+ if isinstance(self.parent, Range):
1756
+ return self.xl.Italic
1757
+ elif isinstance(self.parent, Shape):
1758
+ return True if self.xl.Italic == -1 else False
1759
+ elif isinstance(self.parent.parent, Range):
1760
+ return self.xl.Italic
1761
+ elif isinstance(self.parent.parent, Shape):
1762
+ return True if self.xl.Italic == -1 else False
1763
+ elif isinstance(self.parent.parent.parent, Range):
1764
+ return self.xl.Italic
1765
+ elif isinstance(self.parent.parent.parent, Shape):
1766
+ return True if self.xl.Italic == -1 else False
1767
+
1768
+ @italic.setter
1769
+ def italic(self, value):
1770
+ self.xl.Italic = value
1771
+
1772
+ @property
1773
+ def size(self):
1774
+ return self.xl.Size
1775
+
1776
+ @size.setter
1777
+ def size(self, value):
1778
+ self.xl.Size = value
1779
+
1780
+ @property
1781
+ def color(self):
1782
+ # self.parent is used for direct access, self.parent.parent via characters
1783
+ if isinstance(self.parent, Shape):
1784
+ return int_to_rgb(self.xl.Fill.ForeColor.RGB)
1785
+ elif isinstance(self.parent, Range):
1786
+ return int_to_rgb(self.xl.Color)
1787
+ elif isinstance(self.parent.parent, Shape):
1788
+ return int_to_rgb(self.xl.Fill.ForeColor.RGB)
1789
+ elif isinstance(self.parent.parent, Range):
1790
+ return int_to_rgb(self.xl.Color)
1791
+ elif isinstance(self.parent.parent.parent, Shape):
1792
+ return int_to_rgb(self.xl.Fill.ForeColor.RGB)
1793
+ elif isinstance(self.parent.parent.parent, Range):
1794
+ return int_to_rgb(self.xl.Color)
1795
+
1796
+ @color.setter
1797
+ def color(self, color_or_rgb):
1798
+ # TODO: refactor
1799
+ if self.xl is not None:
1800
+ if isinstance(self.parent, Shape):
1801
+ if isinstance(color_or_rgb, int):
1802
+ self.xl.Fill.ForeColor.RGB = color_or_rgb
1803
+ else:
1804
+ self.xl.Fill.ForeColor.RGB = rgb_to_int(color_or_rgb)
1805
+ elif isinstance(self.parent, Range):
1806
+ if isinstance(color_or_rgb, int):
1807
+ self.xl.Color = color_or_rgb
1808
+ else:
1809
+ self.xl.Color = rgb_to_int(color_or_rgb)
1810
+
1811
+ elif isinstance(self.parent.parent, Shape):
1812
+ if isinstance(color_or_rgb, int):
1813
+ self.xl.Fill.ForeColor.RGB = color_or_rgb
1814
+ else:
1815
+ self.xl.Fill.ForeColor.RGB = rgb_to_int(color_or_rgb)
1816
+ elif isinstance(self.parent.parent, Range):
1817
+ if isinstance(color_or_rgb, int):
1818
+ self.xl.Color = color_or_rgb
1819
+ else:
1820
+ self.xl.Color = rgb_to_int(color_or_rgb)
1821
+
1822
+ elif isinstance(self.parent.parent.parent, Shape):
1823
+ if isinstance(color_or_rgb, int):
1824
+ self.xl.Fill.ForeColor.RGB = color_or_rgb
1825
+ else:
1826
+ self.xl.Fill.ForeColor.RGB = rgb_to_int(color_or_rgb)
1827
+ elif isinstance(self.parent.parent.parent, Range):
1828
+ if isinstance(color_or_rgb, int):
1829
+ self.xl.Color = color_or_rgb
1830
+ else:
1831
+ self.xl.Color = rgb_to_int(color_or_rgb)
1832
+
1833
+ @property
1834
+ def name(self):
1835
+ return self.xl.Name
1836
+
1837
+ @name.setter
1838
+ def name(self, value):
1839
+ self.xl.Name = value
1840
+
1841
+
1842
+ class Characters(base_classes.Characters):
1843
+ def __init__(self, parent, xl, start=None, length=None):
1844
+ self.parent = parent
1845
+ self.xl = xl
1846
+ self.start = start if start else 1
1847
+ self.length = length if length else xl().Count
1848
+
1849
+ @property
1850
+ def api(self):
1851
+ return self.xl(self.start, self.length)
1852
+
1853
+ @property
1854
+ def text(self):
1855
+ return self.xl(self.start, self.length).Text
1856
+
1857
+ @property
1858
+ def font(self):
1859
+ return Font(self, self.xl(self.start, self.length).Font)
1860
+
1861
+ def __getitem__(self, item):
1862
+ if isinstance(item, slice):
1863
+ if (item.start and item.start < 0) or (item.stop and item.stop < 0):
1864
+ raise ValueError(
1865
+ self.__class__.__name__
1866
+ + " object does not support slicing with negative indexes"
1867
+ )
1868
+ start = item.start + 1 if item.start else 1
1869
+ length = item.stop + 1 - start if item.stop else self.length + 1 - start
1870
+ return Characters(parent=self, xl=self.xl, start=start, length=length)
1871
+ else:
1872
+ if item >= 0:
1873
+ return Characters(parent=self, xl=self.xl, start=item + 1, length=1)
1874
+ else:
1875
+ return Characters(
1876
+ parent=self, xl=self.xl, start=len(self.text) + 1 + item, length=1
1877
+ )
1878
+
1879
+
1880
+ class Collection(base_classes.Collection):
1881
+ def __init__(self, xl):
1882
+ self.xl = xl
1883
+
1884
+ @property
1885
+ def api(self):
1886
+ return self.xl
1887
+
1888
+ def __call__(self, key):
1889
+ try:
1890
+ return self._wrap(xl=self.xl.Item(key))
1891
+ except pywintypes.com_error:
1892
+ raise KeyError(key)
1893
+
1894
+ def __len__(self):
1895
+ return self.xl.Count
1896
+
1897
+ def __iter__(self):
1898
+ for xl in self.xl:
1899
+ yield self._wrap(xl=xl)
1900
+
1901
+ def __contains__(self, key):
1902
+ try:
1903
+ self.xl.Item(key)
1904
+ return True
1905
+ except pywintypes.com_error:
1906
+ return False
1907
+
1908
+
1909
+ class PageSetup(base_classes.PageSetup):
1910
+ def __init__(self, xl):
1911
+ self.xl = xl
1912
+
1913
+ @property
1914
+ def api(self):
1915
+ return self.xl
1916
+
1917
+ @property
1918
+ def print_area(self):
1919
+ value = self.xl.PrintArea
1920
+ return None if value == "" else value
1921
+
1922
+ @print_area.setter
1923
+ def print_area(self, value):
1924
+ self.xl.PrintArea = value
1925
+
1926
+
1927
+ class Note(base_classes.Note):
1928
+ def __init__(self, xl):
1929
+ self.xl = xl
1930
+
1931
+ @property
1932
+ def api(self):
1933
+ return self.xl
1934
+
1935
+ @property
1936
+ def text(self):
1937
+ return self.xl.Text()
1938
+
1939
+ @text.setter
1940
+ def text(self, value):
1941
+ self.xl.Text(value)
1942
+
1943
+ def delete(self):
1944
+ self.xl.Delete()
1945
+
1946
+
1947
+ class Shapes(Collection):
1948
+ _wrap = Shape
1949
+
1950
+
1951
+ class Table(base_classes.Table):
1952
+ def __init__(self, xl):
1953
+ self.xl = xl
1954
+
1955
+ @property
1956
+ def api(self):
1957
+ return self.xl
1958
+
1959
+ @property
1960
+ def name(self):
1961
+ return self.xl.Name
1962
+
1963
+ @name.setter
1964
+ def name(self, value):
1965
+ self.xl.Name = value
1966
+
1967
+ @property
1968
+ def data_body_range(self):
1969
+ return Range(xl=self.xl.DataBodyRange) if self.xl.DataBodyRange else None
1970
+
1971
+ @property
1972
+ def display_name(self):
1973
+ return self.xl.DisplayName
1974
+
1975
+ @display_name.setter
1976
+ def display_name(self, value):
1977
+ self.xl.DisplayName = value
1978
+
1979
+ @property
1980
+ def header_row_range(self):
1981
+ return Range(xl=self.xl.HeaderRowRange)
1982
+
1983
+ @property
1984
+ def insert_row_range(self):
1985
+ return Range(xl=self.xl.InsertRowRange)
1986
+
1987
+ @property
1988
+ def parent(self):
1989
+ return Sheet(xl=self.xl.Parent)
1990
+
1991
+ @property
1992
+ def range(self):
1993
+ return Range(xl=self.xl.Range)
1994
+
1995
+ @property
1996
+ def show_autofilter(self):
1997
+ return self.xl.ShowAutoFilter
1998
+
1999
+ @show_autofilter.setter
2000
+ def show_autofilter(self, value):
2001
+ self.xl.ShowAutoFilter = value
2002
+
2003
+ @property
2004
+ def show_headers(self):
2005
+ return self.xl.ShowHeaders
2006
+
2007
+ @show_headers.setter
2008
+ def show_headers(self, value):
2009
+ self.xl.ShowHeaders = value
2010
+
2011
+ @property
2012
+ def show_table_style_column_stripes(self):
2013
+ return self.xl.ShowTableStyleColumnStripes
2014
+
2015
+ @show_table_style_column_stripes.setter
2016
+ def show_table_style_column_stripes(self, value):
2017
+ self.xl.ShowTableStyleColumnStripes = value
2018
+
2019
+ @property
2020
+ def show_table_style_first_column(self):
2021
+ return self.xl.ShowTableStyleFirstColumn
2022
+
2023
+ @show_table_style_first_column.setter
2024
+ def show_table_style_first_column(self, value):
2025
+ self.xl.ShowTableStyleFirstColumn = value
2026
+
2027
+ @property
2028
+ def show_table_style_last_column(self):
2029
+ return self.xl.ShowTableStyleLastColumn
2030
+
2031
+ @show_table_style_last_column.setter
2032
+ def show_table_style_last_column(self, value):
2033
+ self.xl.ShowTableStyleLastColumn = value
2034
+
2035
+ @property
2036
+ def show_table_style_row_stripes(self):
2037
+ return self.xl.ShowTableStyleRowStripes
2038
+
2039
+ @show_table_style_row_stripes.setter
2040
+ def show_table_style_row_stripes(self, value):
2041
+ self.xl.ShowTableStyleRowStripes = value
2042
+
2043
+ @property
2044
+ def show_totals(self):
2045
+ return self.xl.ShowTotals
2046
+
2047
+ @show_totals.setter
2048
+ def show_totals(self, value):
2049
+ self.xl.ShowTotals = value
2050
+
2051
+ @property
2052
+ def table_style(self):
2053
+ return self.xl.TableStyle.Name
2054
+
2055
+ @table_style.setter
2056
+ def table_style(self, value):
2057
+ self.xl.TableStyle = value
2058
+
2059
+ @property
2060
+ def totals_row_range(self):
2061
+ return Range(xl=self.xl.TotalsRowRange)
2062
+
2063
+ def resize(self, range):
2064
+ self.xl.Resize(range.api)
2065
+
2066
+
2067
+ class Tables(Collection, base_classes.Tables):
2068
+ _wrap = Table
2069
+
2070
+ def add(
2071
+ self,
2072
+ source_type=None,
2073
+ source=None,
2074
+ link_source=None,
2075
+ has_headers=None,
2076
+ destination=None,
2077
+ table_style_name=None,
2078
+ name=None,
2079
+ ):
2080
+ table = Table(
2081
+ xl=self.xl.Add(
2082
+ SourceType=ListObjectSourceType.xlSrcRange,
2083
+ Source=source.api,
2084
+ LinkSource=link_source,
2085
+ XlListObjectHasHeaders=True,
2086
+ Destination=destination,
2087
+ TableStyleName=table_style_name,
2088
+ )
2089
+ )
2090
+ if name is not None:
2091
+ table.name = name
2092
+ return table
2093
+
2094
+
2095
+ class Chart(base_classes.Chart):
2096
+ def __init__(self, xl_obj=None, xl=None):
2097
+ self.xl = xl_obj.Chart if xl is None else xl
2098
+ self.xl_obj = xl_obj
2099
+
2100
+ @property
2101
+ def api(self):
2102
+ return self.xl_obj, self.xl
2103
+
2104
+ @property
2105
+ def name(self):
2106
+ if self.xl_obj is None:
2107
+ return self.xl.Name
2108
+ else:
2109
+ return self.xl_obj.Name
2110
+
2111
+ @name.setter
2112
+ def name(self, value):
2113
+ if self.xl_obj is None:
2114
+ self.xl.Name = value
2115
+ else:
2116
+ self.xl_obj.Name = value
2117
+
2118
+ @property
2119
+ def parent(self):
2120
+ if self.xl_obj is None:
2121
+ return Book(xl=self.xl.Parent)
2122
+ else:
2123
+ return Sheet(xl=self.xl_obj.Parent)
2124
+
2125
+ def set_source_data(self, rng):
2126
+ self.xl.SetSourceData(rng.xl)
2127
+
2128
+ @property
2129
+ def chart_type(self):
2130
+ return chart_types_i2s[self.xl.ChartType]
2131
+
2132
+ @chart_type.setter
2133
+ def chart_type(self, chart_type):
2134
+ self.xl.ChartType = chart_types_s2i[chart_type]
2135
+
2136
+ @property
2137
+ def left(self):
2138
+ if self.xl_obj is None:
2139
+ raise Exception("This chart is not embedded.")
2140
+ return self.xl_obj.Left
2141
+
2142
+ @left.setter
2143
+ def left(self, value):
2144
+ if self.xl_obj is None:
2145
+ raise Exception("This chart is not embedded.")
2146
+ self.xl_obj.Left = value
2147
+
2148
+ @property
2149
+ def top(self):
2150
+ if self.xl_obj is None:
2151
+ raise Exception("This chart is not embedded.")
2152
+ return self.xl_obj.Top
2153
+
2154
+ @top.setter
2155
+ def top(self, value):
2156
+ if self.xl_obj is None:
2157
+ raise Exception("This chart is not embedded.")
2158
+ self.xl_obj.Top = value
2159
+
2160
+ @property
2161
+ def width(self):
2162
+ if self.xl_obj is None:
2163
+ raise Exception("This chart is not embedded.")
2164
+ return self.xl_obj.Width
2165
+
2166
+ @width.setter
2167
+ def width(self, value):
2168
+ if self.xl_obj is None:
2169
+ raise Exception("This chart is not embedded.")
2170
+ self.xl_obj.Width = value
2171
+
2172
+ @property
2173
+ def height(self):
2174
+ if self.xl_obj is None:
2175
+ raise Exception("This chart is not embedded.")
2176
+ return self.xl_obj.Height
2177
+
2178
+ @height.setter
2179
+ def height(self, value):
2180
+ if self.xl_obj is None:
2181
+ raise Exception("This chart is not embedded.")
2182
+ self.xl_obj.Height = value
2183
+
2184
+ def delete(self):
2185
+ # todo: what about chart sheets?
2186
+ self.xl_obj.Delete()
2187
+
2188
+ def to_png(self, path):
2189
+ self.xl.Export(path)
2190
+
2191
+ def to_pdf(self, path, quality):
2192
+ self.xl_obj.Select()
2193
+ self.xl.ExportAsFixedFormat(
2194
+ Type=FixedFormatType.xlTypePDF,
2195
+ Filename=path,
2196
+ Quality=quality_types[quality],
2197
+ IncludeDocProperties=True,
2198
+ IgnorePrintAreas=False,
2199
+ OpenAfterPublish=False,
2200
+ )
2201
+ try:
2202
+ self.parent.range("A1").select()
2203
+ except: # noqa: E722
2204
+ pass
2205
+
2206
+
2207
+ class Charts(Collection, base_classes.Charts):
2208
+ def _wrap(self, xl):
2209
+ return Chart(xl_obj=xl)
2210
+
2211
+ def add(self, left, top, width, height):
2212
+ return Chart(xl_obj=self.xl.Add(left, top, width, height))
2213
+
2214
+
2215
+ class Picture(base_classes.Picture):
2216
+ def __init__(self, xl):
2217
+ self.xl = xl
2218
+
2219
+ @property
2220
+ def api(self):
2221
+ return self.xl
2222
+
2223
+ @property
2224
+ def name(self):
2225
+ return self.xl.Name
2226
+
2227
+ @name.setter
2228
+ def name(self, value):
2229
+ self.xl.Name = value
2230
+
2231
+ @property
2232
+ def parent(self):
2233
+ return Sheet(xl=self.xl.Parent)
2234
+
2235
+ @property
2236
+ def left(self):
2237
+ return self.xl.Left
2238
+
2239
+ @left.setter
2240
+ def left(self, value):
2241
+ self.xl.Left = value
2242
+
2243
+ @property
2244
+ def top(self):
2245
+ return self.xl.Top
2246
+
2247
+ @top.setter
2248
+ def top(self, value):
2249
+ self.xl.Top = value
2250
+
2251
+ @property
2252
+ def width(self):
2253
+ return self.xl.Width
2254
+
2255
+ @width.setter
2256
+ def width(self, value):
2257
+ self.xl.Width = value
2258
+
2259
+ @property
2260
+ def height(self):
2261
+ return self.xl.Height
2262
+
2263
+ @height.setter
2264
+ def height(self, value):
2265
+ self.xl.Height = value
2266
+
2267
+ def delete(self):
2268
+ self.xl.Delete()
2269
+
2270
+ @property
2271
+ def lock_aspect_ratio(self):
2272
+ return self.xl.ShapeRange.LockAspectRatio
2273
+
2274
+ @lock_aspect_ratio.setter
2275
+ def lock_aspect_ratio(self, value):
2276
+ self.xl.ShapeRange.LockAspectRatio = value
2277
+
2278
+ def update(self, filename):
2279
+ return utils.excel_update_picture(self, filename)
2280
+
2281
+
2282
+ class Pictures(Collection, base_classes.Pictures):
2283
+ _wrap = Picture
2284
+
2285
+ @property
2286
+ def parent(self):
2287
+ return Sheet(xl=self.xl.Parent)
2288
+
2289
+ def add(
2290
+ self,
2291
+ filename,
2292
+ link_to_file,
2293
+ save_with_document,
2294
+ left,
2295
+ top,
2296
+ width,
2297
+ height,
2298
+ anchor,
2299
+ ):
2300
+ if anchor:
2301
+ top, left = anchor.top, anchor.left
2302
+ else:
2303
+ top = top if top else 0
2304
+ left = left if left else 0
2305
+
2306
+ return Picture(
2307
+ xl=self.xl.Parent.Shapes.AddPicture(
2308
+ Filename=filename,
2309
+ LinkToFile=link_to_file,
2310
+ SaveWithDocument=save_with_document,
2311
+ Left=left,
2312
+ Top=top,
2313
+ Width=width,
2314
+ Height=height,
2315
+ ).DrawingObject
2316
+ )
2317
+
2318
+
2319
+ class Names(base_classes.Names):
2320
+ def __init__(self, xl):
2321
+ self.xl = xl
2322
+
2323
+ @property
2324
+ def api(self):
2325
+ return self.xl
2326
+
2327
+ def __call__(self, name_or_index):
2328
+ return Name(xl=self.xl(name_or_index))
2329
+
2330
+ def contains(self, name_or_index):
2331
+ try:
2332
+ self.xl(name_or_index)
2333
+ except pywintypes.com_error as e:
2334
+ if e.hresult == -2147352567:
2335
+ return False
2336
+ else:
2337
+ raise
2338
+ return True
2339
+
2340
+ def __len__(self):
2341
+ return self.xl.Count
2342
+
2343
+ def add(self, name, refers_to):
2344
+ return Name(xl=self.xl.Add(name, refers_to))
2345
+
2346
+
2347
+ class Name(base_classes.Name):
2348
+ def __init__(self, xl):
2349
+ self.xl = xl
2350
+
2351
+ @property
2352
+ def api(self):
2353
+ return self.xl
2354
+
2355
+ def delete(self):
2356
+ self.xl.Delete()
2357
+
2358
+ @property
2359
+ def name(self):
2360
+ return self.xl.Name
2361
+
2362
+ @name.setter
2363
+ def name(self, value):
2364
+ self.xl.Name = value
2365
+
2366
+ @property
2367
+ def refers_to(self):
2368
+ return self.xl.RefersTo
2369
+
2370
+ @refers_to.setter
2371
+ def refers_to(self, value):
2372
+ self.xl.RefersTo = value
2373
+
2374
+ @property
2375
+ def refers_to_range(self):
2376
+ return Range(xl=self.xl.RefersToRange)
2377
+
2378
+
2379
+ # --- constants ---
2380
+ quality_types = {"minimum": 1, "standard": 0}
2381
+
2382
+ chart_types_s2i = {
2383
+ "3d_area": -4098,
2384
+ "3d_area_stacked": 78,
2385
+ "3d_area_stacked_100": 79,
2386
+ "3d_bar_clustered": 60,
2387
+ "3d_bar_stacked": 61,
2388
+ "3d_bar_stacked_100": 62,
2389
+ "3d_column": -4100,
2390
+ "3d_column_clustered": 54,
2391
+ "3d_column_stacked": 55,
2392
+ "3d_column_stacked_100": 56,
2393
+ "3d_line": -4101,
2394
+ "3d_pie": -4102,
2395
+ "3d_pie_exploded": 70,
2396
+ "area": 1,
2397
+ "area_stacked": 76,
2398
+ "area_stacked_100": 77,
2399
+ "bar_clustered": 57,
2400
+ "bar_of_pie": 71,
2401
+ "bar_stacked": 58,
2402
+ "bar_stacked_100": 59,
2403
+ "bubble": 15,
2404
+ "bubble_3d_effect": 87,
2405
+ "column_clustered": 51,
2406
+ "column_stacked": 52,
2407
+ "column_stacked_100": 53,
2408
+ "cone_bar_clustered": 102,
2409
+ "cone_bar_stacked": 103,
2410
+ "cone_bar_stacked_100": 104,
2411
+ "cone_col": 105,
2412
+ "cone_col_clustered": 99,
2413
+ "cone_col_stacked": 100,
2414
+ "cone_col_stacked_100": 101,
2415
+ "cylinder_bar_clustered": 95,
2416
+ "cylinder_bar_stacked": 96,
2417
+ "cylinder_bar_stacked_100": 97,
2418
+ "cylinder_col": 98,
2419
+ "cylinder_col_clustered": 92,
2420
+ "cylinder_col_stacked": 93,
2421
+ "cylinder_col_stacked_100": 94,
2422
+ "doughnut": -4120,
2423
+ "doughnut_exploded": 80,
2424
+ "line": 4,
2425
+ "line_markers": 65,
2426
+ "line_markers_stacked": 66,
2427
+ "line_markers_stacked_100": 67,
2428
+ "line_stacked": 63,
2429
+ "line_stacked_100": 64,
2430
+ "pie": 5,
2431
+ "pie_exploded": 69,
2432
+ "pie_of_pie": 68,
2433
+ "pyramid_bar_clustered": 109,
2434
+ "pyramid_bar_stacked": 110,
2435
+ "pyramid_bar_stacked_100": 111,
2436
+ "pyramid_col": 112,
2437
+ "pyramid_col_clustered": 106,
2438
+ "pyramid_col_stacked": 107,
2439
+ "pyramid_col_stacked_100": 108,
2440
+ "radar": -4151,
2441
+ "radar_filled": 82,
2442
+ "radar_markers": 81,
2443
+ "stock_hlc": 88,
2444
+ "stock_ohlc": 89,
2445
+ "stock_vhlc": 90,
2446
+ "stock_vohlc": 91,
2447
+ "surface": 83,
2448
+ "surface_top_view": 85,
2449
+ "surface_top_view_wireframe": 86,
2450
+ "surface_wireframe": 84,
2451
+ "xy_scatter": -4169,
2452
+ "xy_scatter_lines": 74,
2453
+ "xy_scatter_lines_no_markers": 75,
2454
+ "xy_scatter_smooth": 72,
2455
+ "xy_scatter_smooth_no_markers": 73,
2456
+ }
2457
+
2458
+ chart_types_i2s = {v: k for k, v in chart_types_s2i.items()}
2459
+
2460
+ directions_s2i = {
2461
+ "d": -4121,
2462
+ "down": -4121,
2463
+ "l": -4159,
2464
+ "left": -4159,
2465
+ "r": -4161,
2466
+ "right": -4161,
2467
+ "u": -4162,
2468
+ "up": -4162,
2469
+ }
2470
+
2471
+ directions_i2s = {-4121: "down", -4159: "left", -4161: "right", -4162: "up"}
2472
+
2473
+ calculation_s2i = {"automatic": -4105, "manual": -4135, "semiautomatic": 2}
2474
+
2475
+ calculation_i2s = {v: k for k, v in calculation_s2i.items()}
2476
+
2477
+ shape_types_s2i = {
2478
+ "3d_model": 30,
2479
+ "auto_shape": 1,
2480
+ "callout": 2,
2481
+ "canvas": 20,
2482
+ "chart": 3,
2483
+ "comment": 4,
2484
+ "content_app": 27,
2485
+ "diagram": 21,
2486
+ "embedded_ole_object": 7,
2487
+ "form_control": 8,
2488
+ "free_form": 5,
2489
+ "graphic": 28,
2490
+ "group": 6,
2491
+ "igx_graphic": 24,
2492
+ "ink": 22,
2493
+ "ink_comment": 23,
2494
+ "line": 9,
2495
+ "linked_3d_model": 31,
2496
+ "linked_graphic": 29,
2497
+ "linked_ole_object": 10,
2498
+ "linked_picture": 11,
2499
+ "media": 16,
2500
+ "ole_control_object": 12,
2501
+ "picture": 13,
2502
+ "placeholder": 14,
2503
+ "script_anchor": 18,
2504
+ "shape_type_mixed": -2,
2505
+ "slicer": 25,
2506
+ "table": 19,
2507
+ "text_box": 17,
2508
+ "text_effect": 15,
2509
+ "web_video": 26,
2510
+ }
2511
+
2512
+ scaling = {
2513
+ "scale_from_top_left": 0,
2514
+ "scale_from_bottom_right": 2,
2515
+ "scale_from_middle": 1,
2516
+ }
2517
+
2518
+ shape_types_i2s = {v: k for k, v in shape_types_s2i.items()}