tpltable 0.3.0__tar.gz → 0.3.2__tar.gz
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.
- {tpltable-0.3.0 → tpltable-0.3.2}/PKG-INFO +1 -1
- {tpltable-0.3.0 → tpltable-0.3.2}/setup.py +1 -1
- tpltable-0.3.2/tpltable/excel.py +561 -0
- {tpltable-0.3.0 → tpltable-0.3.2}/tpltable/main.py +16 -11
- {tpltable-0.3.0 → tpltable-0.3.2}/tpltable/table.py +122 -22
- {tpltable-0.3.0 → tpltable-0.3.2}/tpltable.egg-info/PKG-INFO +1 -1
- tpltable-0.3.0/tpltable/excel.py +0 -208
- {tpltable-0.3.0 → tpltable-0.3.2}/setup.cfg +0 -0
- {tpltable-0.3.0 → tpltable-0.3.2}/tpltable/__init__.py +0 -0
- {tpltable-0.3.0 → tpltable-0.3.2}/tpltable/basic.py +0 -0
- {tpltable-0.3.0 → tpltable-0.3.2}/tpltable/core.py +0 -0
- {tpltable-0.3.0 → tpltable-0.3.2}/tpltable/style.py +0 -0
- {tpltable-0.3.0 → tpltable-0.3.2}/tpltable/style_demo.py +0 -0
- {tpltable-0.3.0 → tpltable-0.3.2}/tpltable.egg-info/SOURCES.txt +0 -0
- {tpltable-0.3.0 → tpltable-0.3.2}/tpltable.egg-info/dependency_links.txt +0 -0
- {tpltable-0.3.0 → tpltable-0.3.2}/tpltable.egg-info/requires.txt +0 -0
- {tpltable-0.3.0 → tpltable-0.3.2}/tpltable.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: tpltable
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: define "Excel" "Table|StyleTable" "Row|Col|StyleRow|StyleCol". Can dynamic modify excel data & styles. Support multiple kinds of indexes. (70%)
|
|
5
5
|
Author-email: 2229066748@qq.com
|
|
6
6
|
Maintainer: Eagle'sBaby
|
|
@@ -5,7 +5,7 @@ from setuptools import find_packages, setup
|
|
|
5
5
|
|
|
6
6
|
setup(
|
|
7
7
|
name='tpltable',
|
|
8
|
-
version='0.3.
|
|
8
|
+
version='0.3.2',
|
|
9
9
|
description='define "Excel" "Table|StyleTable" "Row|Col|StyleRow|StyleCol". Can dynamic modify excel data & styles. Support multiple kinds of indexes. (70%)',
|
|
10
10
|
author_email='2229066748@qq.com',
|
|
11
11
|
maintainer="Eagle'sBaby",
|
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
from tpltable.table import *
|
|
4
|
+
from tpltable.style import *
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ListkeysDict(dict):
|
|
8
|
+
_DEFAULT_PREFIX = "NAME_"
|
|
9
|
+
|
|
10
|
+
def __init__(self, value=None, *, keys: list = None):
|
|
11
|
+
if value is None:
|
|
12
|
+
value = {}
|
|
13
|
+
super().__init__(value)
|
|
14
|
+
self._keys = list(super().keys())
|
|
15
|
+
|
|
16
|
+
if keys is not None:
|
|
17
|
+
for k in keys:
|
|
18
|
+
if k not in self:
|
|
19
|
+
raise KeyError(f"Unknown key:{k} from custom keys")
|
|
20
|
+
|
|
21
|
+
for k in self:
|
|
22
|
+
if k not in keys:
|
|
23
|
+
raise KeyError(f"Mising key:{k} from custom keys")
|
|
24
|
+
|
|
25
|
+
self._keys = list(keys)
|
|
26
|
+
|
|
27
|
+
self._last_key_number = 0
|
|
28
|
+
|
|
29
|
+
def _generate_key(self):
|
|
30
|
+
"""生成一个唯一的键名,确保不与现有键冲突。"""
|
|
31
|
+
self._last_key_number += 1
|
|
32
|
+
key = f"{self._DEFAULT_PREFIX}{self._last_key_number}"
|
|
33
|
+
while key in self:
|
|
34
|
+
key = '_' + key
|
|
35
|
+
return key
|
|
36
|
+
|
|
37
|
+
def _get_keys(self, key) -> list:
|
|
38
|
+
try:
|
|
39
|
+
if isinstance(key, slice):
|
|
40
|
+
return self._keys[key]
|
|
41
|
+
elif isinstance(key, int):
|
|
42
|
+
return [self._keys[key]]
|
|
43
|
+
else:
|
|
44
|
+
return [key]
|
|
45
|
+
except IndexError:
|
|
46
|
+
raise KeyError(f"Key:{key} is out of bound.")
|
|
47
|
+
|
|
48
|
+
# Note: Dicts: ..........................................................................
|
|
49
|
+
def keys(self):
|
|
50
|
+
return self._keys.copy()
|
|
51
|
+
|
|
52
|
+
def values(self):
|
|
53
|
+
return [self[k] for k in self._keys]
|
|
54
|
+
|
|
55
|
+
def items(self):
|
|
56
|
+
return [(k, self[k]) for k in self._keys]
|
|
57
|
+
|
|
58
|
+
def get(self, key, default=None):
|
|
59
|
+
if isinstance(key, int):
|
|
60
|
+
key = self._keys[key]
|
|
61
|
+
elif isinstance(key, str):
|
|
62
|
+
key = key
|
|
63
|
+
else:
|
|
64
|
+
raise TypeError(f"unexpected key type:{type(key)}.")
|
|
65
|
+
return super().get(key, default)
|
|
66
|
+
|
|
67
|
+
def update(self, _m):
|
|
68
|
+
for k, v in _m.items():
|
|
69
|
+
if k not in self:
|
|
70
|
+
self._keys.append(k)
|
|
71
|
+
super().__setitem__(k, v)
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
def fromkeys(cls, keys, value=None):
|
|
75
|
+
return cls({k: value for k in keys})
|
|
76
|
+
|
|
77
|
+
def setdefault(self, __key, __default = None):
|
|
78
|
+
if __key not in self:
|
|
79
|
+
self._keys.append(__key)
|
|
80
|
+
return super().setdefault(__key, __default)
|
|
81
|
+
|
|
82
|
+
def popitem(self):
|
|
83
|
+
key = self._keys.pop()
|
|
84
|
+
return key, super().pop(key)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# Note: Lists: ..........................................................................
|
|
88
|
+
|
|
89
|
+
def append(self, value, *, key: str = None):
|
|
90
|
+
"""在列表末尾添加元素。"""
|
|
91
|
+
if key is None:
|
|
92
|
+
key = self._generate_key()
|
|
93
|
+
if key in self:
|
|
94
|
+
raise KeyError(f"Repeat key: {key}")
|
|
95
|
+
self._keys.append(key)
|
|
96
|
+
super().__setitem__(key, value)
|
|
97
|
+
|
|
98
|
+
def extend(self, _m):
|
|
99
|
+
for k, v in _m.items():
|
|
100
|
+
if k not in self:
|
|
101
|
+
self._keys.append(k)
|
|
102
|
+
super().__setitem__(k, v)
|
|
103
|
+
|
|
104
|
+
def insert(self, index, value, *, key: str = None):
|
|
105
|
+
"""在指定位置插入元素。"""
|
|
106
|
+
if index > len(self):
|
|
107
|
+
raise IndexError("Index out of range.")
|
|
108
|
+
if key is None:
|
|
109
|
+
key = self._generate_key()
|
|
110
|
+
if isinstance(index, str):
|
|
111
|
+
index = self._keys.index(index)
|
|
112
|
+
|
|
113
|
+
if key in self:
|
|
114
|
+
raise KeyError(f"Repeat key: {key}")
|
|
115
|
+
|
|
116
|
+
# self[key] = value
|
|
117
|
+
super().__setitem__(key, value)
|
|
118
|
+
self._keys.insert(index, key)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def index(self, value, start=0, stop=EMPTY_INDEX):
|
|
122
|
+
if stop is EMPTY_INDEX:
|
|
123
|
+
stop = len(self)
|
|
124
|
+
for i, k in enumerate(self._keys[start:stop]):
|
|
125
|
+
if self[k] == value:
|
|
126
|
+
return i
|
|
127
|
+
raise ValueError(f"{value} is not in listkeys_dict.")
|
|
128
|
+
|
|
129
|
+
def remove(self, value):
|
|
130
|
+
i = self.index(value)
|
|
131
|
+
super().__delitem__(self._keys[i])
|
|
132
|
+
self._keys.pop(i)
|
|
133
|
+
|
|
134
|
+
def reverse(self):
|
|
135
|
+
self._keys.reverse()
|
|
136
|
+
|
|
137
|
+
def sort(self, key=None, reverse=False):
|
|
138
|
+
if key is None:
|
|
139
|
+
key = lambda x: x
|
|
140
|
+
self._keys.sort(key=lambda x: key(super().__getitem__(x)), reverse=reverse)
|
|
141
|
+
|
|
142
|
+
def count(self, value):
|
|
143
|
+
return sum([1 for k in self._keys if super().__getitem__(k) == value])
|
|
144
|
+
|
|
145
|
+
# Note: Both Dicts & Lists: ..........................................................................
|
|
146
|
+
def pop(self, key, default=EMPTY_INDEX):
|
|
147
|
+
keys = self._get_keys(key)
|
|
148
|
+
|
|
149
|
+
for key in keys:
|
|
150
|
+
if default is EMPTY_INDEX:
|
|
151
|
+
return super().pop(key)
|
|
152
|
+
else:
|
|
153
|
+
return super().pop(key, default)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def clear(self):
|
|
157
|
+
self._keys.clear()
|
|
158
|
+
super().clear()
|
|
159
|
+
|
|
160
|
+
def copy(self):
|
|
161
|
+
return ListkeysDict(self, keys=self._keys.copy())
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def __getitem__(self, key):
|
|
165
|
+
keys = self._get_keys(key)
|
|
166
|
+
|
|
167
|
+
_ret = []
|
|
168
|
+
for key in keys:
|
|
169
|
+
_ret.append(super().__getitem__(key))
|
|
170
|
+
|
|
171
|
+
if len(_ret) == 1:
|
|
172
|
+
return _ret[0]
|
|
173
|
+
return _ret
|
|
174
|
+
|
|
175
|
+
def __setitem__(self, key, value):
|
|
176
|
+
keys = self._get_keys(key)
|
|
177
|
+
|
|
178
|
+
for key in keys:
|
|
179
|
+
if key not in self:
|
|
180
|
+
self._keys.append(key)
|
|
181
|
+
super().__setitem__(key, value)
|
|
182
|
+
|
|
183
|
+
def __delitem__(self, key):
|
|
184
|
+
keys = self._get_keys(key)
|
|
185
|
+
|
|
186
|
+
for key in keys:
|
|
187
|
+
super().__delitem__(key)
|
|
188
|
+
self._keys.remove(key)
|
|
189
|
+
|
|
190
|
+
def __iter__(self):
|
|
191
|
+
return iter(self.keys())
|
|
192
|
+
|
|
193
|
+
def __len__(self):
|
|
194
|
+
return len(self._keys)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def __str__(self):
|
|
198
|
+
"""提供字典内容的字符串表示。"""
|
|
199
|
+
txt = "{"
|
|
200
|
+
for i, (k, v) in enumerate(self.items()):
|
|
201
|
+
if isinstance(k, str):
|
|
202
|
+
k = f"'{k}'"
|
|
203
|
+
if isinstance(v, str):
|
|
204
|
+
v = f"'{v}'"
|
|
205
|
+
|
|
206
|
+
txt += f"{i}|{k}: {v}, "
|
|
207
|
+
|
|
208
|
+
if txt[-1] != '{':
|
|
209
|
+
txt = txt[:-2]
|
|
210
|
+
|
|
211
|
+
return txt + '}'
|
|
212
|
+
|
|
213
|
+
def __repr__(self):
|
|
214
|
+
return str(self)
|
|
215
|
+
|
|
216
|
+
# MATH
|
|
217
|
+
def __add__(self, other):
|
|
218
|
+
if isinstance(other, ListkeysDict):
|
|
219
|
+
return ListkeysDict({**self, **other})
|
|
220
|
+
return NotImplemented
|
|
221
|
+
|
|
222
|
+
def __radd__(self, other: dict):
|
|
223
|
+
other.update(self)
|
|
224
|
+
return other
|
|
225
|
+
|
|
226
|
+
def __iadd__(self, other):
|
|
227
|
+
self.update(other)
|
|
228
|
+
return self
|
|
229
|
+
|
|
230
|
+
def __sub__(self, other):
|
|
231
|
+
if isinstance(other, ListkeysDict):
|
|
232
|
+
return ListkeysDict({k: v for k, v in self.items() if k not in other})
|
|
233
|
+
return NotImplemented
|
|
234
|
+
|
|
235
|
+
def __rsub__(self, other: dict):
|
|
236
|
+
return {k: v for k, v in other.items() if k not in self}
|
|
237
|
+
|
|
238
|
+
def __isub__(self, other):
|
|
239
|
+
for k in other:
|
|
240
|
+
self.pop(k, None)
|
|
241
|
+
return self
|
|
242
|
+
|
|
243
|
+
def __mul__(self, other):
|
|
244
|
+
raise TypeError("unsupported operand type(s) for *: 'ListkeysDict' and 'int'")
|
|
245
|
+
|
|
246
|
+
def __rmul__(self, other):
|
|
247
|
+
raise TypeError("unsupported operand type(s) for *: 'int' and 'ListkeysDict'")
|
|
248
|
+
|
|
249
|
+
def __imul__(self, other):
|
|
250
|
+
raise TypeError("unsupported operand type(s) for *: 'ListkeysDict' and 'int'")
|
|
251
|
+
|
|
252
|
+
# COMPARE
|
|
253
|
+
def __eq__(self, other):
|
|
254
|
+
if isinstance(other, dict):
|
|
255
|
+
_items0, _items1 = self.items(), other.items()
|
|
256
|
+
return _items0 == _items1
|
|
257
|
+
|
|
258
|
+
return False
|
|
259
|
+
|
|
260
|
+
def __ne__(self, other):
|
|
261
|
+
return not self.__eq__(other)
|
|
262
|
+
|
|
263
|
+
def __lt__(self, other):
|
|
264
|
+
raise TypeError("unorderable types: 'ListkeysDict' < 'ListkeysDict'")
|
|
265
|
+
|
|
266
|
+
def __le__(self, other):
|
|
267
|
+
raise TypeError("unorderable types: 'ListkeysDict' <= 'ListkeysDict'")
|
|
268
|
+
|
|
269
|
+
def __gt__(self, other):
|
|
270
|
+
raise TypeError("unorderable types: 'ListkeysDict' > 'ListkeysDict'")
|
|
271
|
+
|
|
272
|
+
def __ge__(self, other):
|
|
273
|
+
raise TypeError("unorderable types: 'ListkeysDict' >= 'ListkeysDict'")
|
|
274
|
+
|
|
275
|
+
# TYPE
|
|
276
|
+
def __bool__(self):
|
|
277
|
+
return bool(self._keys)
|
|
278
|
+
|
|
279
|
+
def __contains__(self, item):
|
|
280
|
+
return item in self._keys
|
|
281
|
+
|
|
282
|
+
def __hash__(self):
|
|
283
|
+
raise TypeError("unhashable type: 'ListkeysDict'")
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def test_listkeys_dict():
|
|
287
|
+
# 创建listkeys_dict实例
|
|
288
|
+
d = ListkeysDict()
|
|
289
|
+
|
|
290
|
+
# 测试append方法
|
|
291
|
+
d.append('apple')
|
|
292
|
+
d.append('banana')
|
|
293
|
+
print("After appends:", d) # 应显示:['XXX1', 'XXX0'] = ['apple', 'banana']
|
|
294
|
+
|
|
295
|
+
# 测试getitem方法
|
|
296
|
+
print("Get item by int index 0:", d[0]) # 应显示:'XXX0'
|
|
297
|
+
print("Get item by int index 1:", d[1]) # 应显示:'XXX1'
|
|
298
|
+
|
|
299
|
+
# 测试setitem方法
|
|
300
|
+
d[1] = 'cherry'
|
|
301
|
+
print("After setting item 1 to 'cherry':", d) # 应显示:['XXX0', 'XXX_XXX1'] = ['apple', 'cherry']
|
|
302
|
+
|
|
303
|
+
# 测试delitem方法
|
|
304
|
+
del d[0]
|
|
305
|
+
print("After deleting item 0:", d) # 应显示:['XXX_XXX1'] = ['cherry']
|
|
306
|
+
|
|
307
|
+
# 测试insert方法
|
|
308
|
+
d.insert(0, 'date')
|
|
309
|
+
print("After inserting 'date' at index 0:", d) # 应显示:['XXX2', 'XXX_XXX1'] = ['date', 'cherry']
|
|
310
|
+
|
|
311
|
+
# 测试迭代
|
|
312
|
+
print("Iterating over keys:", list(d)) # 应显示:['XXX2', 'XXX_XXX1']
|
|
313
|
+
|
|
314
|
+
# 测试长度
|
|
315
|
+
print("Length of d:", len(d)) # 应显示:2
|
|
316
|
+
|
|
317
|
+
# 测试切片
|
|
318
|
+
print("Slice of d:", d[0:2]) # 应显示:['XXX2', 'XXX_XXX1']
|
|
319
|
+
|
|
320
|
+
# 测试生成key
|
|
321
|
+
print("Generated key:", d._generate_key()) # 应显示一个唯一的key
|
|
322
|
+
|
|
323
|
+
# 测试异常处理
|
|
324
|
+
try:
|
|
325
|
+
d['nonexistent']
|
|
326
|
+
except KeyError as e:
|
|
327
|
+
print("Caught an exception:", e)
|
|
328
|
+
|
|
329
|
+
try:
|
|
330
|
+
del d[10]
|
|
331
|
+
except KeyError as e:
|
|
332
|
+
print("Caught an exception:", e)
|
|
333
|
+
|
|
334
|
+
try:
|
|
335
|
+
d[10] = 'mystery'
|
|
336
|
+
except KeyError as e:
|
|
337
|
+
print("Caught an exception:", e)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
class ExcelFileIOException(Exception):
|
|
341
|
+
pass
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
class Excel(ListkeysDict):
|
|
345
|
+
def __init__(self, fpath: str = None, *, style: bool = True):
|
|
346
|
+
"""
|
|
347
|
+
*Create an Excel object from an excel file or empty
|
|
348
|
+
:param fpath: excel file path. If None, will create an empty Excel object.
|
|
349
|
+
*
|
|
350
|
+
:param style: bool. If True, will load the style of the cell(may be cost). Default is True.
|
|
351
|
+
"""
|
|
352
|
+
self._path = fpath
|
|
353
|
+
if self._path:
|
|
354
|
+
# try:
|
|
355
|
+
wb = load_workbook(fpath)
|
|
356
|
+
tbls = {sheet.title: self.__table_from_sheet(sheet, only_data=not style) for sheet in wb.worksheets}
|
|
357
|
+
wb.close()
|
|
358
|
+
# except Exception as e:
|
|
359
|
+
# raise ExcelFileIOException("\n\nFatal to load excel file: \n" + reinsert_indent(str(e), '\t'))
|
|
360
|
+
else:
|
|
361
|
+
tbls = {}
|
|
362
|
+
|
|
363
|
+
super().__init__(tbls)
|
|
364
|
+
|
|
365
|
+
def __getitem__(self, item):
|
|
366
|
+
if isinstance(item, tuple):
|
|
367
|
+
_i0 = item[0]
|
|
368
|
+
item = item[1:] if len(item) >= 2 else None
|
|
369
|
+
else:
|
|
370
|
+
_i0 = item
|
|
371
|
+
item = None
|
|
372
|
+
|
|
373
|
+
if item is None:
|
|
374
|
+
return super().__getitem__(_i0)
|
|
375
|
+
return super().__getitem__(_i0)[item]
|
|
376
|
+
|
|
377
|
+
def __setitem__(self, key, value):
|
|
378
|
+
if isinstance(key, tuple):
|
|
379
|
+
_i0 = key[0]
|
|
380
|
+
item = key[1:] if len(key) >= 2 else None
|
|
381
|
+
else:
|
|
382
|
+
_i0 = key
|
|
383
|
+
key = None
|
|
384
|
+
|
|
385
|
+
if key is None:
|
|
386
|
+
assert isinstance(value, Table), f"Unexpected Table type:{value}"
|
|
387
|
+
|
|
388
|
+
if item is None:
|
|
389
|
+
super().__setitem__(_i0, value)
|
|
390
|
+
return
|
|
391
|
+
super().__getitem__(_i0)[item] = value
|
|
392
|
+
|
|
393
|
+
def __delitem__(self, key):
|
|
394
|
+
if isinstance(key, tuple):
|
|
395
|
+
_i0 = key[0]
|
|
396
|
+
key = key[1:] if len(key) >= 2 else None
|
|
397
|
+
else:
|
|
398
|
+
_i0 = key
|
|
399
|
+
key = None
|
|
400
|
+
|
|
401
|
+
super().__delitem__(key)
|
|
402
|
+
|
|
403
|
+
def __table_from_sheet(self, sheet: Worksheet, only_data: bool = False):
|
|
404
|
+
_construct_2d_list = []
|
|
405
|
+
_merges = [_merge.coord for _merge in sheet.merged_cells.ranges]
|
|
406
|
+
|
|
407
|
+
if only_data:
|
|
408
|
+
_styles = None
|
|
409
|
+
for row in sheet.iter_rows():
|
|
410
|
+
_construct_2d_list.append([cell.value if cell.value else NaN for cell in row])
|
|
411
|
+
else:
|
|
412
|
+
_styles = []
|
|
413
|
+
for row in sheet.iter_rows():
|
|
414
|
+
_construct_2d_list.append([cell.value if cell.value else NaN for cell in row])
|
|
415
|
+
_styles.append([Style(sheet, cell) for cell in row])
|
|
416
|
+
|
|
417
|
+
return Table(_construct_2d_list, _styles, _merges, copy=False)
|
|
418
|
+
|
|
419
|
+
@property
|
|
420
|
+
def path(self):
|
|
421
|
+
return self._path
|
|
422
|
+
|
|
423
|
+
@property
|
|
424
|
+
def tables(self):
|
|
425
|
+
return list(self.values())
|
|
426
|
+
|
|
427
|
+
@staticmethod
|
|
428
|
+
def __simple_str(string, max: int = 20):
|
|
429
|
+
string = str(string)
|
|
430
|
+
_len = len(string)
|
|
431
|
+
assert max > 4, "Max length should be greater than 4."
|
|
432
|
+
if _len > max:
|
|
433
|
+
_half = max // 2 - 1
|
|
434
|
+
return f"{string[:9]}...{string[_len - 9:]}"
|
|
435
|
+
return string
|
|
436
|
+
|
|
437
|
+
def __repr__(self):
|
|
438
|
+
return f"Excel({self.__simple_str(self._path)}|{len(self)} tables)"
|
|
439
|
+
|
|
440
|
+
def __str__(self):
|
|
441
|
+
_txt = f"Excel({self.__simple_str(self._path)}|{len(self)} tables)\t" + '{\n'
|
|
442
|
+
_MAX_SIZE = 16 - 2
|
|
443
|
+
|
|
444
|
+
# Max length key
|
|
445
|
+
keys = [k for k in self.keys()]
|
|
446
|
+
max_len_key = max([len(key) for key in keys])
|
|
447
|
+
_MAX_SIZE = min(_MAX_SIZE, max_len_key) + 2
|
|
448
|
+
max_len_key = max(max_len_key, _MAX_SIZE)
|
|
449
|
+
|
|
450
|
+
for name, tbl in self.items():
|
|
451
|
+
name = self.__simple_str(name, _MAX_SIZE)
|
|
452
|
+
_left_space = ' ' * (max_len_key - len(name))
|
|
453
|
+
_txt += f"\t'{name}'{_left_space}: {tbl.__repr__()},\n"
|
|
454
|
+
_txt += '}'
|
|
455
|
+
return _txt
|
|
456
|
+
|
|
457
|
+
def detail(self) -> str:
|
|
458
|
+
_txt = f"Excel({self.__simple_str(self._path)}|{len(self)} tables)\t" + '{\n'
|
|
459
|
+
_MAX_SIZE = 16 - 2
|
|
460
|
+
|
|
461
|
+
for name, tbl in self.items():
|
|
462
|
+
_sub_txt = color_string(f"\tTable '{name}'") + " >\n" + reinsert_indent(str(tbl), '\t\t')
|
|
463
|
+
_txt += _sub_txt + '\n\n'
|
|
464
|
+
|
|
465
|
+
if len(self) > 0:
|
|
466
|
+
_txt = _txt[:-1]
|
|
467
|
+
_txt += '}'
|
|
468
|
+
|
|
469
|
+
return _txt
|
|
470
|
+
|
|
471
|
+
def save(self, fpath: str = None, *, style: bool = True):
|
|
472
|
+
"""
|
|
473
|
+
*Save the Excel object to an excel file.
|
|
474
|
+
:param fpath:
|
|
475
|
+
*
|
|
476
|
+
:param style: bool. If True, will save the style of the cell. Default is True.
|
|
477
|
+
NOTE: if use, will slower 20X and more
|
|
478
|
+
:return:
|
|
479
|
+
"""
|
|
480
|
+
wb = Workbook()
|
|
481
|
+
for name, tbl in self.items():
|
|
482
|
+
ws = wb.create_sheet(name)
|
|
483
|
+
for row in tbl:
|
|
484
|
+
_row = []
|
|
485
|
+
for value in row:
|
|
486
|
+
# if nan, set to None
|
|
487
|
+
if isnan(value):
|
|
488
|
+
_row.append(None)
|
|
489
|
+
else:
|
|
490
|
+
_row.append(value)
|
|
491
|
+
ws.append(_row)
|
|
492
|
+
|
|
493
|
+
# Merge
|
|
494
|
+
for merge in tbl.merges:
|
|
495
|
+
ws.merge_cells(f"{merge[0].letter}:{merge[1].letter}")
|
|
496
|
+
|
|
497
|
+
# Style
|
|
498
|
+
if style and tbl.styles is not None:
|
|
499
|
+
style_tbl = tbl.styles
|
|
500
|
+
for i, row in enumerate(ws.iter_rows()):
|
|
501
|
+
for j, cell in enumerate(row):
|
|
502
|
+
style_tbl[i][j].apply(ws, cell)
|
|
503
|
+
|
|
504
|
+
# remove the default sheet
|
|
505
|
+
wb.remove(wb.active)
|
|
506
|
+
|
|
507
|
+
if not fpath:
|
|
508
|
+
fpath = self._path
|
|
509
|
+
if not fpath:
|
|
510
|
+
raise ExcelFileIOException("\n\nNo file path defined: " + str(self))
|
|
511
|
+
|
|
512
|
+
try:
|
|
513
|
+
wb.save(fpath)
|
|
514
|
+
except Exception as e:
|
|
515
|
+
raise ExcelFileIOException("\n\nFatal to save excel file: \n" + reinsert_indent(str(e), '\t'))
|
|
516
|
+
wb.close()
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
if __name__ == '__main__':
|
|
520
|
+
# test_listkeys_dict()
|
|
521
|
+
# exit()
|
|
522
|
+
# lk = ListkeysDict({'a': "Hello, ", 'b': "World"})
|
|
523
|
+
# print(lk['a'])
|
|
524
|
+
# print(lk)
|
|
525
|
+
# lk.append('你好!')
|
|
526
|
+
# print(lk)
|
|
527
|
+
# lk.insert(0, 'awa')
|
|
528
|
+
# print(lk)
|
|
529
|
+
# del lk[:2]
|
|
530
|
+
# print(lk)
|
|
531
|
+
#
|
|
532
|
+
# exit()
|
|
533
|
+
from ffre import FileFinder
|
|
534
|
+
|
|
535
|
+
fdir = r"C:\Users\22290\Desktop\20240504整理\tpltable 数据"
|
|
536
|
+
ff = FileFinder(fdir)
|
|
537
|
+
fpaths = list(ff.find(".xlsx"))
|
|
538
|
+
|
|
539
|
+
if not fpaths:
|
|
540
|
+
print("No excel file found.")
|
|
541
|
+
exit(0)
|
|
542
|
+
|
|
543
|
+
fp = fpaths[1]
|
|
544
|
+
_t = TimeRecorder()
|
|
545
|
+
for i in range(1):
|
|
546
|
+
excel = Excel(fp, style=False)
|
|
547
|
+
print("Load excel without style cost:", _t.dms(1), "ms")
|
|
548
|
+
_t.tick()
|
|
549
|
+
for i in range(1):
|
|
550
|
+
excel = Excel(fp, style=True)
|
|
551
|
+
print("Load excel with style cost:", _t.dms(1), "ms")
|
|
552
|
+
|
|
553
|
+
excel[0].styles['A1'][TYPE_COL_WIDTH] = 200
|
|
554
|
+
|
|
555
|
+
print(excel)
|
|
556
|
+
_t.tick()
|
|
557
|
+
excel.save("test.xlsx", style=False)
|
|
558
|
+
print("Save excel without style cost:", _t.dms(1), "ms")
|
|
559
|
+
_t.tick()
|
|
560
|
+
excel.save("test_style.xlsx", style=True)
|
|
561
|
+
print("Save excel with style cost:", _t.dms(1), "ms")
|
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
from ffre import FileFinder, FCollect
|
|
2
1
|
from tpltable.excel import Excel, Table
|
|
3
2
|
from tpltable.style import *
|
|
4
|
-
DIR_PATH = r"C:\Users\22290\Desktop\20240504整理\tpltable 数据"
|
|
5
3
|
|
|
6
|
-
|
|
4
|
+
e = Excel()
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
# e = Excel()
|
|
10
|
-
#
|
|
11
6
|
# t = e.append(Table(
|
|
12
7
|
# [[0],
|
|
13
8
|
# [4, 5, 6, 7],
|
|
@@ -22,15 +17,25 @@ xlsx_paths:FCollect = FileFinder(DIR_PATH).find("xlsx", pattern=".+线$", exclud
|
|
|
22
17
|
# t.styles['A1'].set(TYPE_COL_WIDTH, 40) # 设置列宽
|
|
23
18
|
# t.styles[:, 1].set(TYPE_BCOLOR, COLOR_LIGHTGRAY) # 设置浅灰色背景色
|
|
24
19
|
# t.merge("A1:D1") # 合并单元格
|
|
20
|
+
#
|
|
21
|
+
# print(S
|
|
22
|
+
# e.detail()
|
|
23
|
+
# )
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
t = e[0]
|
|
25
|
+
t = e.append(Table())
|
|
28
26
|
|
|
29
|
-
|
|
27
|
+
t.append(
|
|
28
|
+
[1, 2, 3, 4, 10],
|
|
29
|
+
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
|
30
|
+
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
|
31
|
+
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
|
32
|
+
)
|
|
30
33
|
|
|
31
|
-
|
|
34
|
+
t.merge('A1:F2')
|
|
35
|
+
t.merge('E2:H3')
|
|
36
|
+
print(t)
|
|
32
37
|
|
|
33
|
-
e.save()
|
|
38
|
+
e.save('test.xlsx')
|
|
34
39
|
|
|
35
40
|
|
|
36
41
|
|
|
@@ -409,7 +409,31 @@ class _ExcelIndexsLike_2DArray:
|
|
|
409
409
|
return tuple()
|
|
410
410
|
|
|
411
411
|
def __str__(self):
|
|
412
|
-
|
|
412
|
+
_values = self._df.values.tolist()
|
|
413
|
+
_values2 = []
|
|
414
|
+
|
|
415
|
+
# make sure each one is string
|
|
416
|
+
for i, _l in enumerate(_values):
|
|
417
|
+
_values[i] = [str(v) if not isnan(v) else "NaN" for v in _l]
|
|
418
|
+
_values2.append([f"({v})" if not isnan(v) else "NaN" for v in _l])
|
|
419
|
+
|
|
420
|
+
_cnd = np.array(_values)
|
|
421
|
+
_bracket_cdf = np.array(_values2)
|
|
422
|
+
|
|
423
|
+
# 将处于merge范围内的元素加上(), 除了第一个元素
|
|
424
|
+
_unhandled_mask = np.ones(self._df.shape, dtype=bool)
|
|
425
|
+
for lt, rb in self._merges:
|
|
426
|
+
_new_mask = np.zeros(self._df.shape, dtype=bool)
|
|
427
|
+
_new_mask[lt.ir:rb.ir + 1, lt.ic:rb.ic + 1] = _unhandled_mask[lt.ir:rb.ir + 1, lt.ic:rb.ic + 1]
|
|
428
|
+
_cnd[_new_mask] = _bracket_cdf[_new_mask]
|
|
429
|
+
# 把第一个元素还原
|
|
430
|
+
if _unhandled_mask[lt.ir, lt.ic]:
|
|
431
|
+
_cnd[lt.ir, lt.ic] = ':' + _cnd[lt.ir, lt.ic][1:]
|
|
432
|
+
_unhandled_mask[lt.ir:rb.ir + 1, lt.ic:rb.ic + 1] = False
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
return str(pd.DataFrame(_cnd, index=self._df.index, columns=self._df.columns, copy=False))
|
|
436
|
+
|
|
413
437
|
|
|
414
438
|
def __repr__(self):
|
|
415
439
|
c = Coord(self._df.shape[1] - 1, self._df.shape[0] - 1)
|
|
@@ -734,7 +758,12 @@ class _ExcelIndexsLike_2DArray:
|
|
|
734
758
|
|
|
735
759
|
def _reindex(self):
|
|
736
760
|
_2d_list = self._df.values.tolist()
|
|
737
|
-
self.
|
|
761
|
+
self._reconstruct(_2d_list, *self._other_raws, copy=False)
|
|
762
|
+
|
|
763
|
+
def _reconstruct(self, *args, **kwargs):
|
|
764
|
+
new = self.__class__(*args, **kwargs)
|
|
765
|
+
self._df = new._df
|
|
766
|
+
return new
|
|
738
767
|
|
|
739
768
|
def _enlarge(self, ir, ic, fill=np.nan):
|
|
740
769
|
"""
|
|
@@ -763,7 +792,7 @@ class _ExcelIndexsLike_2DArray:
|
|
|
763
792
|
if ic >= sc:
|
|
764
793
|
for _l in _2d_list:
|
|
765
794
|
_l.extend([fill] * (ic - sc))
|
|
766
|
-
self.
|
|
795
|
+
self._reconstruct(_2d_list, *self._other_raws, copy=False)
|
|
767
796
|
|
|
768
797
|
def __getitem__(self, key):
|
|
769
798
|
"""
|
|
@@ -819,7 +848,7 @@ class _ExcelIndexsLike_2DArray:
|
|
|
819
848
|
|
|
820
849
|
if _i0 is None: # None, None
|
|
821
850
|
if isinstance(value, (int, float, str, bool)):
|
|
822
|
-
self._df = value
|
|
851
|
+
self._df.iloc[:, :] = value # NOTE: 使用:,:来避免修改形状
|
|
823
852
|
return
|
|
824
853
|
elif isinstance(value, list):
|
|
825
854
|
value = np.array(value)
|
|
@@ -832,7 +861,7 @@ class _ExcelIndexsLike_2DArray:
|
|
|
832
861
|
if value.shape == self._df.shape:
|
|
833
862
|
self._df.iloc[:, :] = value
|
|
834
863
|
else:
|
|
835
|
-
self.
|
|
864
|
+
self._reconstruct(value, *self._other_raws, copy=False)
|
|
836
865
|
return
|
|
837
866
|
else:
|
|
838
867
|
raise ValueError(f"Unsupported value type: {type(value)}")
|
|
@@ -865,7 +894,7 @@ class _ExcelIndexsLike_2DArray:
|
|
|
865
894
|
else:
|
|
866
895
|
raise ValueError(f"Unsupported value type: {type(value)}")
|
|
867
896
|
_2d_list = _2d_list[:_start] + _ + _2d_list[_start:]
|
|
868
|
-
self.
|
|
897
|
+
self._reconstruct(_2d_list, *self._other_raws, copy=False)
|
|
869
898
|
return
|
|
870
899
|
else:
|
|
871
900
|
raise ValueError(f"Unsupported value type: {type(value)}")
|
|
@@ -880,7 +909,7 @@ class _ExcelIndexsLike_2DArray:
|
|
|
880
909
|
"""
|
|
881
910
|
_i0, _i1 = self._dim2_parse_index_key(key)
|
|
882
911
|
if _i0 is None:
|
|
883
|
-
self.
|
|
912
|
+
self._reconstruct(None, *self._other_raws)
|
|
884
913
|
return
|
|
885
914
|
elif _i1 is None:
|
|
886
915
|
self._df.drop(self._df.index[_i0], inplace=True)
|
|
@@ -891,7 +920,7 @@ class _ExcelIndexsLike_2DArray:
|
|
|
891
920
|
_is_i1_slice = isinstance(_i1, slice)
|
|
892
921
|
if _is_i0_slice and _is_i1_slice:
|
|
893
922
|
if _i0.start is None and _i0.stop is None and _i1.start is None and _i1.stop is None:
|
|
894
|
-
self.
|
|
923
|
+
self._reconstruct()
|
|
895
924
|
elif _i0.start is None and _i0.stop is None:
|
|
896
925
|
self._df.drop(self._df.columns[_i1], axis=1, inplace=True)
|
|
897
926
|
self._reindex()
|
|
@@ -911,6 +940,7 @@ class _ExcelIndexsLike_2DArray:
|
|
|
911
940
|
else:
|
|
912
941
|
self._df.iloc[_i0, _i1] = np.nan
|
|
913
942
|
|
|
943
|
+
|
|
914
944
|
def append(self, *values, axis=0):
|
|
915
945
|
"""
|
|
916
946
|
Append Rows to the end of the table
|
|
@@ -925,14 +955,14 @@ class _ExcelIndexsLike_2DArray:
|
|
|
925
955
|
else:
|
|
926
956
|
for row in _construct:
|
|
927
957
|
row.extend(values)
|
|
928
|
-
self.
|
|
958
|
+
self._reconstruct(_construct, *self._other_raws, copy=False)
|
|
929
959
|
|
|
930
960
|
def clear(self):
|
|
931
961
|
"""
|
|
932
962
|
Clear the table
|
|
933
963
|
:return: None
|
|
934
964
|
"""
|
|
935
|
-
self.
|
|
965
|
+
self._reconstruct(None, *self._other_raws)
|
|
936
966
|
|
|
937
967
|
def copy(self):
|
|
938
968
|
"""
|
|
@@ -967,7 +997,7 @@ class _ExcelIndexsLike_2DArray:
|
|
|
967
997
|
|
|
968
998
|
if _r == 0 or _c == 0:
|
|
969
999
|
_construct = [[fill.copy() if _has_fill_copy else fill for _ in range(_new_c)] for _ in range(_new_r)]
|
|
970
|
-
self.
|
|
1000
|
+
self._reconstruct(_construct, *self._other_raws, copy=False)
|
|
971
1001
|
return
|
|
972
1002
|
|
|
973
1003
|
# 先考虑扩张
|
|
@@ -978,9 +1008,9 @@ class _ExcelIndexsLike_2DArray:
|
|
|
978
1008
|
|
|
979
1009
|
# 再考虑缩小 NOTE: 缩小不必考虑label的问题
|
|
980
1010
|
if _r > _new_r:
|
|
981
|
-
self.
|
|
1011
|
+
self._reconstruct(self._df.iloc[:_new_r], *self._other_raws, copy=False)
|
|
982
1012
|
if _c > _new_c:
|
|
983
|
-
self.
|
|
1013
|
+
self._reconstruct(self._df.iloc[:, :_new_c], *self._other_raws, copy=False)
|
|
984
1014
|
|
|
985
1015
|
|
|
986
1016
|
def __iter__(self):
|
|
@@ -1071,7 +1101,7 @@ class Table(_ExcelIndexsLike_2DArray):
|
|
|
1071
1101
|
def merges(self):
|
|
1072
1102
|
return self._merges
|
|
1073
1103
|
|
|
1074
|
-
def merge(self, key:U[int, str, slice,
|
|
1104
|
+
def merge(self, key:U[int, str, slice, None], key1:U[int, str, slice, None]=EMPTY_INDEX):
|
|
1075
1105
|
if key1 is not EMPTY_INDEX:
|
|
1076
1106
|
key = key, key1
|
|
1077
1107
|
|
|
@@ -1098,17 +1128,28 @@ class Table(_ExcelIndexsLike_2DArray):
|
|
|
1098
1128
|
lt, rb = Coord(_i1, _i0), Coord(_i1 - 1, _i0 - 1)
|
|
1099
1129
|
|
|
1100
1130
|
# check whether the merge is out of the table
|
|
1101
|
-
if lt.ir > ishape[
|
|
1131
|
+
if lt.ir > ishape[0] or lt.ic > ishape[1]:
|
|
1102
1132
|
return
|
|
1103
1133
|
|
|
1104
|
-
if rb.ir > ishape[
|
|
1105
|
-
rb.ir = ishape[
|
|
1134
|
+
if rb.ir > ishape[0]:
|
|
1135
|
+
rb.ir = ishape[0]
|
|
1106
1136
|
|
|
1107
|
-
if rb.ic > ishape[
|
|
1108
|
-
rb.ic = ishape[
|
|
1137
|
+
if rb.ic > ishape[1]:
|
|
1138
|
+
rb.ic = ishape[1]
|
|
1109
1139
|
|
|
1110
1140
|
self._merges.append((lt, rb))
|
|
1111
1141
|
|
|
1142
|
+
def _reconstruct(self, *args, **kwargs):
|
|
1143
|
+
"""
|
|
1144
|
+
用于缩小或扩大表格时自动同步调整styles
|
|
1145
|
+
:param args:
|
|
1146
|
+
:param kwargs:
|
|
1147
|
+
:return:
|
|
1148
|
+
"""
|
|
1149
|
+
new = super()._reconstruct(*args, **kwargs)
|
|
1150
|
+
self._styles = new._styles
|
|
1151
|
+
return new
|
|
1152
|
+
|
|
1112
1153
|
def __delitem__(self, key):
|
|
1113
1154
|
"""
|
|
1114
1155
|
Delete the value of the key
|
|
@@ -1117,11 +1158,12 @@ class Table(_ExcelIndexsLike_2DArray):
|
|
|
1117
1158
|
"""
|
|
1118
1159
|
_i0, _i1 = self._dim2_parse_index_key(key)
|
|
1119
1160
|
if _i0 is None:
|
|
1120
|
-
self.
|
|
1161
|
+
self._reconstruct(None, *self._other_raws, copy=False)
|
|
1121
1162
|
return
|
|
1122
1163
|
elif _i1 is None:
|
|
1123
1164
|
self._df.drop(self._df.index[_i0], inplace=True)
|
|
1124
|
-
self._styles.drop(self._styles.index[_i0], inplace=True)
|
|
1165
|
+
self._styles._df.drop(self._styles._df.index[_i0], inplace=True)
|
|
1166
|
+
self._remerge('dr', _i0)
|
|
1125
1167
|
self._reindex()
|
|
1126
1168
|
return
|
|
1127
1169
|
else:
|
|
@@ -1129,26 +1171,84 @@ class Table(_ExcelIndexsLike_2DArray):
|
|
|
1129
1171
|
_is_i1_slice = isinstance(_i1, slice)
|
|
1130
1172
|
if _is_i0_slice and _is_i1_slice:
|
|
1131
1173
|
if _i0.start is None and _i0.stop is None and _i1.start is None and _i1.stop is None:
|
|
1132
|
-
self.
|
|
1174
|
+
self._reconstruct(None, *self._other_raws, copy=False)
|
|
1133
1175
|
elif _i0.start is None and _i0.stop is None:
|
|
1134
1176
|
self._df.drop(self._df.columns[_i1], axis=1, inplace=True)
|
|
1177
|
+
self._styles._df.drop(self._styles._df.columns[_i1], axis=1, inplace=True)
|
|
1178
|
+
self._remerge('dc', _i1)
|
|
1135
1179
|
self._reindex()
|
|
1136
1180
|
elif _i1.start is None and _i1.stop is None:
|
|
1137
1181
|
self._df.drop(self._df.index[_i0], inplace=True)
|
|
1182
|
+
self._styles._df.drop(self._styles._df.index[_i0], inplace=True)
|
|
1183
|
+
self._remerge('dr', _i0)
|
|
1138
1184
|
self._reindex()
|
|
1139
1185
|
else: # 删除一片区域为nan
|
|
1140
1186
|
self._df.iloc[_i0, _i1] = np.nan
|
|
1141
1187
|
elif _is_i0_slice and _i0.start is None and _i0.stop is None:
|
|
1142
1188
|
self._df.drop(self._df.columns[_i1], axis=1, inplace=True)
|
|
1189
|
+
self._styles._df.drop(self._styles._df.columns[_i1], axis=1, inplace=True)
|
|
1190
|
+
self._remerge('dc', _i1)
|
|
1143
1191
|
self._reindex()
|
|
1144
1192
|
return
|
|
1145
1193
|
elif _is_i1_slice and _i1.start is None and _i1.stop is None:
|
|
1146
1194
|
self._df.drop(self._df.index[_i0], inplace=True)
|
|
1195
|
+
self._styles._df.drop(self._styles._df.index[_i0], inplace=True)
|
|
1196
|
+
self._remerge('dr', _i0)
|
|
1147
1197
|
self._reindex()
|
|
1148
1198
|
return
|
|
1149
1199
|
else:
|
|
1150
1200
|
self._df.iloc[_i0, _i1] = np.nan
|
|
1151
1201
|
|
|
1202
|
+
def _remerge(self, op_type, value):
|
|
1203
|
+
"""
|
|
1204
|
+
重新调整合并区域
|
|
1205
|
+
:param op_type: 'a'|'d'+'r'|'c' 表示增加行、删除行、增加列、删除列
|
|
1206
|
+
:param value: 对应的索引值,可以是int或slice
|
|
1207
|
+
:return: None
|
|
1208
|
+
"""
|
|
1209
|
+
assert len(op_type) == 2, f"Invalid op_type: {op_type}"
|
|
1210
|
+
op, tar = op_type
|
|
1211
|
+
assert op in 'ad' and tar in 'rc', f"Invalid op_type: {op_type}"
|
|
1212
|
+
|
|
1213
|
+
# 将int转为slice
|
|
1214
|
+
if isinstance(value, int):
|
|
1215
|
+
value = slice(value, value + 1)
|
|
1216
|
+
elif not isinstance(value, slice):
|
|
1217
|
+
raise ValueError(f"Unsupported re_merge value type: {type(value)}")
|
|
1218
|
+
_removes = []
|
|
1219
|
+
if tar == 'r':
|
|
1220
|
+
for i, (lt, rb) in enumerate(self._merges.copy()):
|
|
1221
|
+
if op == 'a':
|
|
1222
|
+
if lt.ir >= value.start:
|
|
1223
|
+
lt.ir += value.stop - value.start
|
|
1224
|
+
if rb.ir >= value.start:
|
|
1225
|
+
rb.ir += value.stop - value.start
|
|
1226
|
+
else:
|
|
1227
|
+
if lt.ir >= value.start:
|
|
1228
|
+
lt.ir -= value.stop - value.start
|
|
1229
|
+
if rb.ir >= value.start:
|
|
1230
|
+
rb.ir -= value.stop - value.start
|
|
1231
|
+
if lt.ir == rb.ir:
|
|
1232
|
+
_removes.append(i)
|
|
1233
|
+
# else: NOTE: 无需赋值, 只是浅拷贝
|
|
1234
|
+
# self._merges[i] = (lt, rb)
|
|
1235
|
+
else:
|
|
1236
|
+
for i, (lt, rb) in enumerate(self._merges.copy()):
|
|
1237
|
+
if op == 'a':
|
|
1238
|
+
if lt.ic >= value.start:
|
|
1239
|
+
lt.ic += value.stop - value.start
|
|
1240
|
+
if rb.ic >= value.start:
|
|
1241
|
+
rb.ic += value.stop - value.start
|
|
1242
|
+
else:
|
|
1243
|
+
if lt.ic >= value.start:
|
|
1244
|
+
lt.ic -= value.stop - value.start
|
|
1245
|
+
if rb.ic >= value.start:
|
|
1246
|
+
rb.ic -= value.stop - value.start
|
|
1247
|
+
if lt.ic == rb.ic:
|
|
1248
|
+
_removes.append(i)
|
|
1249
|
+
# else: NOTE: 无需赋值, 只是浅拷贝
|
|
1250
|
+
# self._merges[i] = (lt, rb)
|
|
1251
|
+
|
|
1152
1252
|
|
|
1153
1253
|
def __1d_test(_print=True):
|
|
1154
1254
|
d = [1, 2, 3, 4, 5]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: tpltable
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: define "Excel" "Table|StyleTable" "Row|Col|StyleRow|StyleCol". Can dynamic modify excel data & styles. Support multiple kinds of indexes. (70%)
|
|
5
5
|
Author-email: 2229066748@qq.com
|
|
6
6
|
Maintainer: Eagle'sBaby
|
tpltable-0.3.0/tpltable/excel.py
DELETED
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
import numpy as np
|
|
2
|
-
import pandas as pd
|
|
3
|
-
from tpltable.table import *
|
|
4
|
-
from tpltable.style import *
|
|
5
|
-
|
|
6
|
-
class ExcelFileIOException(Exception):
|
|
7
|
-
pass
|
|
8
|
-
|
|
9
|
-
class Excel(dict):
|
|
10
|
-
def __init__(self, fpath:str=None, *, style:bool=True):
|
|
11
|
-
"""
|
|
12
|
-
*Create an Excel object from an excel file or empty
|
|
13
|
-
:param fpath: excel file path. If None, will create an empty Excel object.
|
|
14
|
-
*
|
|
15
|
-
:param style: bool. If True, will load the style of the cell(may be cost). Default is True.
|
|
16
|
-
"""
|
|
17
|
-
self._path = fpath
|
|
18
|
-
if self._path:
|
|
19
|
-
# try:
|
|
20
|
-
wb = load_workbook(fpath)
|
|
21
|
-
tbls = {sheet.title: self.__table_from_sheet(sheet, only_data=not style) for sheet in wb.worksheets}
|
|
22
|
-
wb.close()
|
|
23
|
-
# except Exception as e:
|
|
24
|
-
# raise ExcelFileIOException("\n\nFatal to load excel file: \n" + reinsert_indent(str(e), '\t'))
|
|
25
|
-
else:
|
|
26
|
-
tbls = {}
|
|
27
|
-
|
|
28
|
-
super().__init__(tbls)
|
|
29
|
-
|
|
30
|
-
# 添加数字索引
|
|
31
|
-
self.numbers = {i: tbl for i, (name, tbl) in enumerate(tbls.items())}
|
|
32
|
-
|
|
33
|
-
def __getitem__(self, item):
|
|
34
|
-
if isinstance(item, int):
|
|
35
|
-
return self.numbers[item]
|
|
36
|
-
return super().__getitem__(item)
|
|
37
|
-
|
|
38
|
-
def __setitem__(self, key, value):
|
|
39
|
-
if isinstance(key, int):
|
|
40
|
-
key = list(self.keys())[key]
|
|
41
|
-
super().__setitem__(key, value)
|
|
42
|
-
|
|
43
|
-
def __delitem__(self, key):
|
|
44
|
-
if isinstance(key, int):
|
|
45
|
-
key = list(self.keys())[key]
|
|
46
|
-
del self.numbers[key]
|
|
47
|
-
super().__delitem__(key)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def __table_from_sheet(self, sheet:Worksheet, only_data:bool=False):
|
|
51
|
-
_construct_2d_list = []
|
|
52
|
-
_merges = [_merge.coord for _merge in sheet.merged_cells.ranges]
|
|
53
|
-
|
|
54
|
-
if only_data:
|
|
55
|
-
_styles = None
|
|
56
|
-
for row in sheet.iter_rows():
|
|
57
|
-
_construct_2d_list.append([cell.value if cell.value else NaN for cell in row])
|
|
58
|
-
else:
|
|
59
|
-
_styles = []
|
|
60
|
-
for row in sheet.iter_rows():
|
|
61
|
-
_construct_2d_list.append([cell.value if cell.value else NaN for cell in row])
|
|
62
|
-
_styles.append([Style(sheet, cell) for cell in row])
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
return Table(_construct_2d_list, _styles, _merges, copy=False)
|
|
66
|
-
|
|
67
|
-
@property
|
|
68
|
-
def path(self):
|
|
69
|
-
return self._path
|
|
70
|
-
|
|
71
|
-
@property
|
|
72
|
-
def tables(self):
|
|
73
|
-
return list(self.values())
|
|
74
|
-
|
|
75
|
-
@staticmethod
|
|
76
|
-
def __simple_str(string, max:int=20):
|
|
77
|
-
_len = len(string)
|
|
78
|
-
assert max > 4, "Max length should be greater than 4."
|
|
79
|
-
if _len > max:
|
|
80
|
-
_half = max // 2 - 1
|
|
81
|
-
return f"{string[:9]}...{string[_len-9:]}"
|
|
82
|
-
return string
|
|
83
|
-
|
|
84
|
-
def __repr__(self):
|
|
85
|
-
return f"Excel({self.__simple_str(self._path)}|{len(self)} tables)"
|
|
86
|
-
|
|
87
|
-
def __str__(self):
|
|
88
|
-
_txt = f"Excel({self.__simple_str(self._path)}|{len(self)} tables)\t" + '{\n'
|
|
89
|
-
_MAX_SIZE = 16 - 2
|
|
90
|
-
|
|
91
|
-
# Max length key
|
|
92
|
-
keys = [k for k in self.keys()]
|
|
93
|
-
max_len_key = max([len(key) for key in keys])
|
|
94
|
-
_MAX_SIZE = min(_MAX_SIZE, max_len_key) + 2
|
|
95
|
-
max_len_key = max(max_len_key, _MAX_SIZE)
|
|
96
|
-
|
|
97
|
-
for name, tbl in self.items():
|
|
98
|
-
name = self.__simple_str(name, _MAX_SIZE)
|
|
99
|
-
_left_space = ' ' * (max_len_key - len(name))
|
|
100
|
-
_txt += f"\t'{name}'{_left_space}: {tbl.__repr__()},\n"
|
|
101
|
-
_txt += '}'
|
|
102
|
-
return _txt
|
|
103
|
-
|
|
104
|
-
def append(self, tbl:Table, name:str=None):
|
|
105
|
-
if name is None:
|
|
106
|
-
name = f"sheet{len(self)}"
|
|
107
|
-
while name in self:
|
|
108
|
-
name = '_' + name
|
|
109
|
-
self.numbers[len(self)] = tbl
|
|
110
|
-
self[name] = tbl
|
|
111
|
-
return tbl
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
def detail(self) -> str:
|
|
115
|
-
_txt = f"Excel({self.__simple_str(self._path)}|{len(self)} tables)\t" + '{\n'
|
|
116
|
-
_MAX_SIZE = 16 - 2
|
|
117
|
-
|
|
118
|
-
for name, tbl in self.items():
|
|
119
|
-
_sub_txt = color_string(f"\tTable '{name}'") + " >\n" + reinsert_indent(str(tbl), '\t\t')
|
|
120
|
-
_txt += _sub_txt + '\n\n'
|
|
121
|
-
|
|
122
|
-
if len(self) > 0:
|
|
123
|
-
_txt = _txt[:-1]
|
|
124
|
-
_txt += '}'
|
|
125
|
-
|
|
126
|
-
return _txt
|
|
127
|
-
|
|
128
|
-
def save(self, fpath:str=None, *, style:bool=True):
|
|
129
|
-
"""
|
|
130
|
-
*Save the Excel object to an excel file.
|
|
131
|
-
:param fpath:
|
|
132
|
-
*
|
|
133
|
-
:param style: bool. If True, will save the style of the cell. Default is True.
|
|
134
|
-
NOTE: if use, will slower 20X and more
|
|
135
|
-
:return:
|
|
136
|
-
"""
|
|
137
|
-
wb = Workbook()
|
|
138
|
-
for name, tbl in self.items():
|
|
139
|
-
ws = wb.create_sheet(name)
|
|
140
|
-
for row in tbl:
|
|
141
|
-
_row = []
|
|
142
|
-
for value in row:
|
|
143
|
-
# if nan, set to None
|
|
144
|
-
if isnan(value):
|
|
145
|
-
_row.append(None)
|
|
146
|
-
else:
|
|
147
|
-
_row.append(value)
|
|
148
|
-
ws.append(_row)
|
|
149
|
-
|
|
150
|
-
# Merge
|
|
151
|
-
for merge in tbl.merges:
|
|
152
|
-
ws.merge_cells(f"{merge[0].letter}:{merge[1].letter}")
|
|
153
|
-
|
|
154
|
-
# Style
|
|
155
|
-
if style and tbl.styles is not None:
|
|
156
|
-
style_tbl = tbl.styles
|
|
157
|
-
for i, row in enumerate(ws.iter_rows()):
|
|
158
|
-
for j, cell in enumerate(row):
|
|
159
|
-
style_tbl[i][j].apply(ws, cell)
|
|
160
|
-
|
|
161
|
-
# remove the default sheet
|
|
162
|
-
wb.remove(wb.active)
|
|
163
|
-
|
|
164
|
-
if not fpath:
|
|
165
|
-
fpath = self._path
|
|
166
|
-
if not fpath:
|
|
167
|
-
raise ExcelFileIOException("\n\nNo file path defined: " + str(self))
|
|
168
|
-
|
|
169
|
-
try:
|
|
170
|
-
wb.save(fpath)
|
|
171
|
-
except Exception as e:
|
|
172
|
-
raise ExcelFileIOException("\n\nFatal to save excel file: \n" + reinsert_indent(str(e), '\t'))
|
|
173
|
-
wb.close()
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if __name__ == '__main__':
|
|
177
|
-
from ffre import FileFinder
|
|
178
|
-
fdir = r"C:\Users\22290\Desktop\20240504整理\tpltable 数据"
|
|
179
|
-
ff = FileFinder(fdir)
|
|
180
|
-
fpaths = list(ff.find(".xlsx"))
|
|
181
|
-
|
|
182
|
-
if not fpaths:
|
|
183
|
-
print("No excel file found.")
|
|
184
|
-
exit(0)
|
|
185
|
-
|
|
186
|
-
fp = fpaths[1]
|
|
187
|
-
_t = TimeRecorder()
|
|
188
|
-
for i in range(1):
|
|
189
|
-
excel = Excel(fp, style=False)
|
|
190
|
-
print("Load excel without style cost:", _t.dms(1), "ms")
|
|
191
|
-
_t.tick()
|
|
192
|
-
for i in range(1):
|
|
193
|
-
excel = Excel(fp, style=True)
|
|
194
|
-
print("Load excel with style cost:", _t.dms(1), "ms")
|
|
195
|
-
|
|
196
|
-
excel[0].styles['A1'][TYPE_COL_WIDTH] = 200
|
|
197
|
-
|
|
198
|
-
print(excel)
|
|
199
|
-
_t.tick()
|
|
200
|
-
excel.save("test.xlsx", style=False)
|
|
201
|
-
print("Save excel without style cost:", _t.dms(1), "ms")
|
|
202
|
-
_t.tick()
|
|
203
|
-
excel.save("test_style.xlsx", style=True)
|
|
204
|
-
print("Save excel with style cost:", _t.dms(1), "ms")
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|