pyxlsbwriter 0.0.1__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.
- pyxlsbwriter-0.0.1/PKG-INFO +48 -0
- pyxlsbwriter-0.0.1/README.md +32 -0
- pyxlsbwriter-0.0.1/pyproject.toml +31 -0
- pyxlsbwriter-0.0.1/setup.cfg +4 -0
- pyxlsbwriter-0.0.1/src/pyxlsbwriter/__init__.py +3 -0
- pyxlsbwriter-0.0.1/src/pyxlsbwriter/writer.py +618 -0
- pyxlsbwriter-0.0.1/src/pyxlsbwriter.egg-info/PKG-INFO +48 -0
- pyxlsbwriter-0.0.1/src/pyxlsbwriter.egg-info/SOURCES.txt +10 -0
- pyxlsbwriter-0.0.1/src/pyxlsbwriter.egg-info/dependency_links.txt +1 -0
- pyxlsbwriter-0.0.1/src/pyxlsbwriter.egg-info/requires.txt +7 -0
- pyxlsbwriter-0.0.1/src/pyxlsbwriter.egg-info/top_level.txt +1 -0
- pyxlsbwriter-0.0.1/tests/test_real_data.py +102 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyxlsbwriter
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A Python library to write XLSB files.
|
|
5
|
+
Author: Krzysztof Duśko
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: xlsxwriter
|
|
12
|
+
Provides-Extra: test
|
|
13
|
+
Requires-Dist: pyodbc; extra == "test"
|
|
14
|
+
Provides-Extra: examples
|
|
15
|
+
Requires-Dist: memory-profiler; extra == "examples"
|
|
16
|
+
|
|
17
|
+
# Python XLSB Writer
|
|
18
|
+
|
|
19
|
+
A Python library for writing large data sets to XLSB files efficiently.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install pyxlsbwriter
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from pyxlsbwriter import XlsbWriter
|
|
31
|
+
import datetime
|
|
32
|
+
from decimal import Decimal
|
|
33
|
+
|
|
34
|
+
data = [
|
|
35
|
+
["Name", "Age", "City", "info"],
|
|
36
|
+
[-123, 2147483647, 2147483648, 2147483999],
|
|
37
|
+
["x", "y", "z", datetime.datetime.today()],
|
|
38
|
+
["Alice", 25, "New York", datetime.date.today()],
|
|
39
|
+
["Bob", 30, "London", Decimal(3.14)],
|
|
40
|
+
["Charlie", 35, "Paris", datetime.datetime.now()],
|
|
41
|
+
[True, False, None, datetime.datetime.utcnow()]
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
writer = XlsbWriter("test.xlsb")
|
|
45
|
+
writer.add_sheet("Sheet1")
|
|
46
|
+
writer.write_sheet(data)
|
|
47
|
+
writer.save()
|
|
48
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Python XLSB Writer
|
|
2
|
+
|
|
3
|
+
A Python library for writing large data sets to XLSB files efficiently.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install pyxlsbwriter
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from pyxlsbwriter import XlsbWriter
|
|
15
|
+
import datetime
|
|
16
|
+
from decimal import Decimal
|
|
17
|
+
|
|
18
|
+
data = [
|
|
19
|
+
["Name", "Age", "City", "info"],
|
|
20
|
+
[-123, 2147483647, 2147483648, 2147483999],
|
|
21
|
+
["x", "y", "z", datetime.datetime.today()],
|
|
22
|
+
["Alice", 25, "New York", datetime.date.today()],
|
|
23
|
+
["Bob", 30, "London", Decimal(3.14)],
|
|
24
|
+
["Charlie", 35, "Paris", datetime.datetime.now()],
|
|
25
|
+
[True, False, None, datetime.datetime.utcnow()]
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
writer = XlsbWriter("test.xlsb")
|
|
29
|
+
writer.add_sheet("Sheet1")
|
|
30
|
+
writer.write_sheet(data)
|
|
31
|
+
writer.save()
|
|
32
|
+
```
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pyxlsbwriter"
|
|
7
|
+
version = "0.0.1"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Krzysztof Duśko"},
|
|
10
|
+
]
|
|
11
|
+
description = "A Python library to write XLSB files."
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.8"
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
]
|
|
19
|
+
dependencies = [
|
|
20
|
+
"xlsxwriter"
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[project.optional-dependencies]
|
|
24
|
+
test = [
|
|
25
|
+
"pyodbc"
|
|
26
|
+
]
|
|
27
|
+
examples = [
|
|
28
|
+
"memory-profiler"
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
|
|
@@ -0,0 +1,618 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
import datetime
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
import zipfile
|
|
5
|
+
import struct
|
|
6
|
+
from typing import Tuple, Iterable
|
|
7
|
+
import io
|
|
8
|
+
import itertools
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class FilterData:
|
|
12
|
+
sheet_index: int = 0
|
|
13
|
+
start_column: int = 0
|
|
14
|
+
end_column: int = 0
|
|
15
|
+
start_row: int = 0
|
|
16
|
+
end_row: int = 0
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class XlsbWriter:
|
|
20
|
+
def __init__(self, filename: str):
|
|
21
|
+
self.filename = filename
|
|
22
|
+
self._worksheet_data: list[Tuple[str, Iterable[list[any]],bool]] = []
|
|
23
|
+
self._shared_strings: list[str] = []
|
|
24
|
+
self._shared_strings_dict: dict[str, int] = {}
|
|
25
|
+
self._sheet_count = 0
|
|
26
|
+
self._sst_unique_count = 0
|
|
27
|
+
self._sst_all_count = 0
|
|
28
|
+
self._filtered_data_list: list[FilterData] = []
|
|
29
|
+
|
|
30
|
+
def add_sheet(self, sheet_name: str):
|
|
31
|
+
self._sheet_count += 1
|
|
32
|
+
# Add a placeholder for the data iterable
|
|
33
|
+
self._worksheet_data.append((sheet_name, iter([])))
|
|
34
|
+
|
|
35
|
+
def write_sheet(self, data: Iterable[list[any]], hidden = False, compressionLevel:int = 4):
|
|
36
|
+
if not self._worksheet_data:
|
|
37
|
+
self.add_sheet("Sheet1")
|
|
38
|
+
|
|
39
|
+
# Store the iterable without consuming it. Processing is deferred to save().
|
|
40
|
+
sheet_name, _ = self._worksheet_data[self._sheet_count - 1]
|
|
41
|
+
self._worksheet_data[self._sheet_count - 1] = (sheet_name, data,hidden)
|
|
42
|
+
|
|
43
|
+
if self._zf is None:
|
|
44
|
+
self._zf = zipfile.ZipFile(self.filename, 'w', zipfile.ZIP_DEFLATED, compresslevel=compressionLevel)
|
|
45
|
+
|
|
46
|
+
for i, (sheet_name, sheet_data_iterable, _) in enumerate([self._worksheet_data[self._sheet_count - 1]]):
|
|
47
|
+
i = self._sheet_count - 1
|
|
48
|
+
sheet_id = i + 1
|
|
49
|
+
with self._zf.open(f"xl/worksheets/sheet{sheet_id}.bin", 'w') as sheet_file:
|
|
50
|
+
self._write_worksheet_bin(sheet_file, sheet_data_iterable, i)
|
|
51
|
+
|
|
52
|
+
self._zf.writestr(f"xl/worksheets/_rels/sheet{sheet_id}.bin.rels", self._create_worksheet_rels(sheet_id))
|
|
53
|
+
self._zf.writestr(f"xl/worksheets/binaryIndex{sheet_id}.bin", self._binaryIndexBin)
|
|
54
|
+
|
|
55
|
+
_zf: zipfile.ZipFile|None = None
|
|
56
|
+
|
|
57
|
+
def save(self):
|
|
58
|
+
self._zf.writestr("[Content_Types].xml", self._create_content_types())
|
|
59
|
+
self._zf.writestr("_rels/.rels", self._create_root_rels())
|
|
60
|
+
self._zf.writestr("xl/workbook.bin", self._create_workbook_bin())
|
|
61
|
+
self._zf.writestr("xl/styles.bin", self._stylesBin)
|
|
62
|
+
self._zf.writestr("xl/_rels/workbook.bin.rels", self._create_workbook_rels())
|
|
63
|
+
|
|
64
|
+
# After all worksheets are processed, the shared strings table is complete.
|
|
65
|
+
# Now, write it to the zip file in a streaming fashion.
|
|
66
|
+
with self._zf.open("xl/sharedStrings.bin", 'w') as sst_file:
|
|
67
|
+
self._write_shared_strings_bin(sst_file)
|
|
68
|
+
|
|
69
|
+
self._zf.close()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _create_content_types(self) -> str:
|
|
73
|
+
parts = "".join(
|
|
74
|
+
f'<Override PartName="/xl/worksheets/sheet{i + 1}.bin" ContentType="application/vnd.ms-excel.worksheet"/>'
|
|
75
|
+
f'<Override PartName="/xl/worksheets/binaryIndex{i + 1}.bin" ContentType="application/vnd.ms-excel.binIndexWs"/>'
|
|
76
|
+
for i in range(self._sheet_count)
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
return f'''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
80
|
+
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
|
|
81
|
+
<Default Extension="bin" ContentType="application/vnd.ms-excel.sheet.binary.macroEnabled.main"/>
|
|
82
|
+
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
|
|
83
|
+
<Default Extension="xml" ContentType="application/xml"/>
|
|
84
|
+
<Override PartName="/xl/workbook.bin" ContentType="application/vnd.ms-excel.sheet.binary.macroEnabled.main"/>
|
|
85
|
+
{parts}
|
|
86
|
+
<Override PartName="/xl/styles.bin" ContentType="application/vnd.ms-excel.styles"/>
|
|
87
|
+
<Override PartName="/xl/sharedStrings.bin" ContentType="application/vnd.ms-excel.sharedStrings"/>
|
|
88
|
+
</Types>'''
|
|
89
|
+
|
|
90
|
+
def _create_root_rels(self) -> str:
|
|
91
|
+
return '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
92
|
+
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
|
93
|
+
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.bin"/>
|
|
94
|
+
</Relationships>'''
|
|
95
|
+
|
|
96
|
+
def _create_workbook_rels(self) -> str:
|
|
97
|
+
relationships = [
|
|
98
|
+
f'<Relationship Id="rId{i + 1}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet{i + 1}.bin"/>'
|
|
99
|
+
for i in range(self._sheet_count)
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
style_rid = self._sheet_count + 1
|
|
103
|
+
shared_strings_rid = self._sheet_count + 2
|
|
104
|
+
relationships.append(f'<Relationship Id="rId{style_rid}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.bin"/>')
|
|
105
|
+
relationships.append(f'<Relationship Id="rId{shared_strings_rid}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="sharedStrings.bin"/>')
|
|
106
|
+
|
|
107
|
+
return f'''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
108
|
+
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
|
109
|
+
{"".join(relationships)}
|
|
110
|
+
</Relationships>'''
|
|
111
|
+
|
|
112
|
+
def _create_worksheet_rels(self, sheet_id) -> str:
|
|
113
|
+
return f'''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
114
|
+
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
|
115
|
+
<Relationship Id="rId1" Type="http://schemas.microsoft.com/office/2006/relationships/xlBinaryIndex" Target="binaryIndex{sheet_id}.bin"/>
|
|
116
|
+
</Relationships>'''
|
|
117
|
+
|
|
118
|
+
def _create_workbook_bin(self) -> bytes:
|
|
119
|
+
data = bytearray(self._workbookBinStart)
|
|
120
|
+
for i, (sheet_name, _, hidden) in enumerate(self._worksheet_data):
|
|
121
|
+
sheet_id = i + 1
|
|
122
|
+
r_id = f"rId{sheet_id}"
|
|
123
|
+
|
|
124
|
+
record_length = 4 + 3 * 4 + len(sheet_name) * 2 + len(r_id) * 2
|
|
125
|
+
|
|
126
|
+
data.extend(b'\x9C\x01')
|
|
127
|
+
data.extend(self._write_custom_variable_int(record_length))
|
|
128
|
+
|
|
129
|
+
if hidden:
|
|
130
|
+
data.extend(struct.pack('<I', 1))
|
|
131
|
+
else:
|
|
132
|
+
data.extend(struct.pack('<I', 0))
|
|
133
|
+
|
|
134
|
+
data.extend(struct.pack('<I', sheet_id))
|
|
135
|
+
data.extend(struct.pack('<I', len(r_id)))
|
|
136
|
+
data.extend(r_id.encode('utf-16-le'))
|
|
137
|
+
data.extend(struct.pack('<I', len(sheet_name)))
|
|
138
|
+
data.extend(sheet_name.encode('utf-16-le'))
|
|
139
|
+
|
|
140
|
+
data.extend(self._workbookBinMiddle)
|
|
141
|
+
self._write_filter_defined_names(data);
|
|
142
|
+
data.extend(self._workbookBinEnd)
|
|
143
|
+
return bytes(data)
|
|
144
|
+
|
|
145
|
+
def _write_custom_variable_int(self, value: int) -> bytes:
|
|
146
|
+
if value < 0:
|
|
147
|
+
raise ValueError("Value must be non-negative")
|
|
148
|
+
|
|
149
|
+
buffer = bytearray()
|
|
150
|
+
temp_val = value
|
|
151
|
+
while True:
|
|
152
|
+
byte = temp_val & 0x7F
|
|
153
|
+
temp_val >>= 7
|
|
154
|
+
if temp_val > 0:
|
|
155
|
+
byte |= 0x80
|
|
156
|
+
buffer.append(byte)
|
|
157
|
+
else:
|
|
158
|
+
buffer.append(byte)
|
|
159
|
+
break
|
|
160
|
+
return bytes(buffer)
|
|
161
|
+
|
|
162
|
+
def _write_shared_strings_bin(self, sst_file: io.BufferedWriter):
|
|
163
|
+
buffer = io.BytesIO()
|
|
164
|
+
BUFFER_SIZE = 65536
|
|
165
|
+
|
|
166
|
+
buffer.write(b'\x9F\x01\x08')
|
|
167
|
+
buffer.write(struct.pack('<II', self._sst_all_count, self._sst_unique_count))
|
|
168
|
+
|
|
169
|
+
for s in self._shared_strings:
|
|
170
|
+
str_len = len(s)
|
|
171
|
+
if str_len > 32767:
|
|
172
|
+
str_len = 32767
|
|
173
|
+
s = s[:str_len]
|
|
174
|
+
|
|
175
|
+
record_length = 1 + 4 + 2 * str_len
|
|
176
|
+
|
|
177
|
+
buffer.write(b'\x13')
|
|
178
|
+
buffer.write(self._write_custom_variable_int(record_length))
|
|
179
|
+
buffer.write(b'\x00')
|
|
180
|
+
buffer.write(struct.pack('<I', str_len))
|
|
181
|
+
buffer.write(s.encode('utf-16-le'))
|
|
182
|
+
|
|
183
|
+
if buffer.tell() > BUFFER_SIZE:
|
|
184
|
+
sst_file.write(buffer.getvalue())
|
|
185
|
+
buffer.seek(0)
|
|
186
|
+
buffer.truncate()
|
|
187
|
+
|
|
188
|
+
buffer.write(b'\xA0\x01\x00')
|
|
189
|
+
|
|
190
|
+
if buffer.tell() > 0:
|
|
191
|
+
sst_file.write(buffer.getvalue())
|
|
192
|
+
|
|
193
|
+
def _write_cols_width(self, buffer: io.BytesIO, start_col: int, end_col: int, col_widths_array: list[float]):
|
|
194
|
+
"""
|
|
195
|
+
Write column width information to the buffer.
|
|
196
|
+
Based on the C# WriteColsWidth method from XlsbWriter.cs
|
|
197
|
+
"""
|
|
198
|
+
# widths !!!
|
|
199
|
+
buffer.write(b'\x86\x03') # 134, 3
|
|
200
|
+
|
|
201
|
+
l = 0
|
|
202
|
+
for i in range(start_col, end_col):
|
|
203
|
+
# start of column definition
|
|
204
|
+
buffer.write(b'\x00\x3C\x12') # 0, 60, 18
|
|
205
|
+
|
|
206
|
+
# column min
|
|
207
|
+
buffer.write(struct.pack('<I', i))
|
|
208
|
+
# column max
|
|
209
|
+
buffer.write(struct.pack('<I', i))
|
|
210
|
+
|
|
211
|
+
# width
|
|
212
|
+
buffer.write(b'\x00')
|
|
213
|
+
buffer.write(struct.pack('<B', int(col_widths_array[l]))) # .. x 7 = pixels
|
|
214
|
+
buffer.write(b'\x00\x00')
|
|
215
|
+
|
|
216
|
+
buffer.write(b'\x00\x00\x00\x00')
|
|
217
|
+
buffer.write(b'\x02') # column properties /hidden etc, 2 = normal
|
|
218
|
+
# end of column definition
|
|
219
|
+
l += 1
|
|
220
|
+
|
|
221
|
+
buffer.write(b'\x00\x87\x03\x00') # 0, 135, 3, 0
|
|
222
|
+
|
|
223
|
+
_sheetCnt = 1
|
|
224
|
+
def _write_worksheet_bin(self, sheet_file: io.BufferedWriter, worksheet_data: Iterable[list[any]], worksheet_index:int):
|
|
225
|
+
buffer = io.BytesIO()
|
|
226
|
+
BUFFER_SIZE = 65536
|
|
227
|
+
|
|
228
|
+
buffer.write(self._sheet1Bytes[:54])
|
|
229
|
+
if self._sheetCnt != 1:
|
|
230
|
+
buffer.write(b'\x9C')
|
|
231
|
+
else:
|
|
232
|
+
buffer.write(b'\xDC')
|
|
233
|
+
self._sheetCnt +=1
|
|
234
|
+
|
|
235
|
+
buffer.write(self._sheet1Bytes[55:84])
|
|
236
|
+
|
|
237
|
+
buffer.write(self._stickHeaderA1bytes)
|
|
238
|
+
|
|
239
|
+
# Calculate column information by reading first 100 rows for better width estimation
|
|
240
|
+
data_iterator = iter(worksheet_data)
|
|
241
|
+
rows_to_analyze = []
|
|
242
|
+
max_cols = 1
|
|
243
|
+
|
|
244
|
+
# Read up to 100 rows for analysis
|
|
245
|
+
for _ in range(100):
|
|
246
|
+
try:
|
|
247
|
+
row = next(data_iterator)
|
|
248
|
+
rows_to_analyze.append(row)
|
|
249
|
+
max_cols = max(max_cols, len(row) if row else 0)
|
|
250
|
+
except StopIteration:
|
|
251
|
+
break # No more rows available
|
|
252
|
+
|
|
253
|
+
if max_cols == 0:
|
|
254
|
+
max_cols = 1
|
|
255
|
+
|
|
256
|
+
start_col = 0
|
|
257
|
+
end_col = max_cols
|
|
258
|
+
|
|
259
|
+
# Create column widths based on analyzed rows content
|
|
260
|
+
col_widths_array = []
|
|
261
|
+
for i in range(max_cols):
|
|
262
|
+
max_width = 0
|
|
263
|
+
for row in rows_to_analyze:
|
|
264
|
+
cell = row[i]
|
|
265
|
+
if i < len(row) and cell is not None:
|
|
266
|
+
if cell is Decimal:
|
|
267
|
+
cell = float(cell)
|
|
268
|
+
lenn = len(str(cell))
|
|
269
|
+
temp_width = 1.25 * lenn + 4
|
|
270
|
+
max_width = max(max_width, temp_width)
|
|
271
|
+
|
|
272
|
+
if max_width == 0:
|
|
273
|
+
max_width = 1.25 * 0 + 4 # Empty column
|
|
274
|
+
col_widths_array.append(max_width)
|
|
275
|
+
|
|
276
|
+
# Create iterator that starts with analyzed rows, then continues with the rest
|
|
277
|
+
if rows_to_analyze:
|
|
278
|
+
data_iterator = itertools.chain(rows_to_analyze, data_iterator)
|
|
279
|
+
else:
|
|
280
|
+
data_iterator = iter([])
|
|
281
|
+
|
|
282
|
+
buffer.write(self._sheet1Bytes[84:159])
|
|
283
|
+
|
|
284
|
+
# Write column widths
|
|
285
|
+
self._write_cols_width(buffer, start_col, end_col, col_widths_array)
|
|
286
|
+
|
|
287
|
+
buffer.write(self._sheet1Bytes[159:175])
|
|
288
|
+
buffer.write(b'\x26\x00')
|
|
289
|
+
buffer.write(b'\x91\x01\x00')
|
|
290
|
+
|
|
291
|
+
shared_strings_dict = self._shared_strings_dict
|
|
292
|
+
shared_strings_append = self._shared_strings.append
|
|
293
|
+
|
|
294
|
+
for row_idx, row in enumerate(data_iterator):
|
|
295
|
+
last_col = len(row) - 1 if row else 0
|
|
296
|
+
|
|
297
|
+
buffer.write(b'\x00\x19')
|
|
298
|
+
buffer.write(struct.pack('<I', row_idx))
|
|
299
|
+
buffer.write(b'\x00\x00\x00\x00')
|
|
300
|
+
buffer.write(struct.pack('<I', 0x12c))
|
|
301
|
+
buffer.write(b'\x00\x01\x00\x00\x00')
|
|
302
|
+
buffer.write(struct.pack('<II', 0, last_col))
|
|
303
|
+
|
|
304
|
+
for col_idx, cell in enumerate(row):
|
|
305
|
+
if cell is None:
|
|
306
|
+
continue
|
|
307
|
+
if isinstance(cell, str):
|
|
308
|
+
self._sst_all_count += 1
|
|
309
|
+
if cell not in shared_strings_dict:
|
|
310
|
+
string_index = self._sst_unique_count
|
|
311
|
+
shared_strings_dict[cell] = string_index
|
|
312
|
+
shared_strings_append(cell)
|
|
313
|
+
self._sst_unique_count += 1
|
|
314
|
+
else:
|
|
315
|
+
string_index = shared_strings_dict[cell]
|
|
316
|
+
|
|
317
|
+
style_ref = 3 if row_idx == 0 else 0
|
|
318
|
+
buffer.write(b'\x07\x0C')
|
|
319
|
+
buffer.write(struct.pack('<III', col_idx, style_ref, string_index))
|
|
320
|
+
elif isinstance(cell, bool):
|
|
321
|
+
buffer.write(b'\x04\x09')
|
|
322
|
+
buffer.write(struct.pack('<IIB', col_idx, 0, 1 if cell else 0))
|
|
323
|
+
elif isinstance(cell, int):
|
|
324
|
+
if -536870912 <= cell <= 536870911:
|
|
325
|
+
rk_val = (cell << 2) | 0b10
|
|
326
|
+
buffer.write(b'\x02\x0C')
|
|
327
|
+
buffer.write(struct.pack('<IIi', col_idx, 0, rk_val))
|
|
328
|
+
else:
|
|
329
|
+
buffer.write(b'\x05\x10')
|
|
330
|
+
buffer.write(struct.pack('<IId', col_idx, 0, float(cell)))
|
|
331
|
+
elif isinstance(cell, float):
|
|
332
|
+
buffer.write(b'\x05\x10')
|
|
333
|
+
buffer.write(struct.pack('<IId', col_idx, 0, cell))
|
|
334
|
+
elif isinstance(cell, Decimal):
|
|
335
|
+
buffer.write(b'\x05\x10')
|
|
336
|
+
buffer.write(struct.pack('<IId', col_idx, 0, float(cell)))
|
|
337
|
+
elif isinstance(cell, datetime.datetime):
|
|
338
|
+
excel_date = (cell - datetime.datetime(1899, 12, 30)).total_seconds() / 86400.0
|
|
339
|
+
buffer.write(b'\x05\x10')
|
|
340
|
+
buffer.write(struct.pack('<IId', col_idx, 1, excel_date))
|
|
341
|
+
elif isinstance(cell, datetime.date):
|
|
342
|
+
excel_date = (datetime.datetime.combine(cell, datetime.time()) - datetime.datetime(1899, 12, 30)).total_seconds() / 86400.0
|
|
343
|
+
buffer.write(b'\x05\x10')
|
|
344
|
+
buffer.write(struct.pack('<IId', col_idx, 2, excel_date))
|
|
345
|
+
else:
|
|
346
|
+
cell_str = str(cell)
|
|
347
|
+
self._sst_all_count += 1
|
|
348
|
+
if cell_str not in shared_strings_dict:
|
|
349
|
+
string_index = self._sst_unique_count
|
|
350
|
+
shared_strings_dict[cell_str] = string_index
|
|
351
|
+
shared_strings_append(cell_str)
|
|
352
|
+
self._sst_unique_count += 1
|
|
353
|
+
else:
|
|
354
|
+
string_index = shared_strings_dict[cell_str]
|
|
355
|
+
buffer.write(b'\x07\x0C')
|
|
356
|
+
buffer.write(struct.pack('<III', col_idx, 0, string_index))
|
|
357
|
+
|
|
358
|
+
if buffer.tell() > BUFFER_SIZE:
|
|
359
|
+
sheet_file.write(buffer.getvalue())
|
|
360
|
+
buffer.seek(0)
|
|
361
|
+
buffer.truncate()
|
|
362
|
+
|
|
363
|
+
buffer.write(self._sheet1Bytes[218:290])
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
# doAutofilter
|
|
367
|
+
buffer.write(self._autoFilterStartBytes)
|
|
368
|
+
buffer.write(struct.pack('<I', 0))
|
|
369
|
+
buffer.write(struct.pack('<I', row_idx))
|
|
370
|
+
buffer.write(struct.pack('<I', 0))
|
|
371
|
+
buffer.write(struct.pack('<I', 0 + end_col - 1))
|
|
372
|
+
buffer.write(self._autoFilterEndBytes)
|
|
373
|
+
self._filtered_data_list.append(
|
|
374
|
+
FilterData(
|
|
375
|
+
sheet_index=worksheet_index,
|
|
376
|
+
start_column=0,
|
|
377
|
+
end_column=end_col - 1,
|
|
378
|
+
start_row=0,
|
|
379
|
+
end_row=row_idx
|
|
380
|
+
))
|
|
381
|
+
|
|
382
|
+
buffer.write(self._sheet1Bytes[290:])
|
|
383
|
+
if buffer.tell() > 0:
|
|
384
|
+
sheet_file.write(buffer.getvalue())
|
|
385
|
+
|
|
386
|
+
def _write_filter_defined_names(self, sw: bytearray) -> None:
|
|
387
|
+
filtered_dict_items_cnt = len(self._filtered_data_list) if self._filtered_data_list else 0
|
|
388
|
+
|
|
389
|
+
# Temporary fix for https://github.com/KrzysztofDusko/SpreadSheetTasks/issues/2
|
|
390
|
+
if (filtered_dict_items_cnt >= 0 and
|
|
391
|
+
(0x80 + (filtered_dict_items_cnt - 21) * 0x0c) <= 255):
|
|
392
|
+
|
|
393
|
+
sw.extend(self._magicFilterExcel2016Fix0)
|
|
394
|
+
|
|
395
|
+
if filtered_dict_items_cnt <= 10:
|
|
396
|
+
# !!! ? for cnt <=10
|
|
397
|
+
sw.extend(bytes([0x10 + (filtered_dict_items_cnt - 1) * 0x0c,
|
|
398
|
+
filtered_dict_items_cnt]))
|
|
399
|
+
sw.extend(bytes([0x00, 0x00, 0x00]))
|
|
400
|
+
|
|
401
|
+
elif filtered_dict_items_cnt <= 20:
|
|
402
|
+
sw.extend(bytes([0x10 + (filtered_dict_items_cnt - 1) * 0x0c,
|
|
403
|
+
(filtered_dict_items_cnt - 1) // 10,
|
|
404
|
+
filtered_dict_items_cnt]))
|
|
405
|
+
sw.extend(bytes([0x00, 0x00, 0x00]))
|
|
406
|
+
|
|
407
|
+
else: # ???
|
|
408
|
+
sw.extend(bytes([0x80 + (filtered_dict_items_cnt - 21) * 0x0c,
|
|
409
|
+
(filtered_dict_items_cnt - 1) // 10,
|
|
410
|
+
filtered_dict_items_cnt]))
|
|
411
|
+
sw.extend(bytes([0x00, 0x00, 0x00]))
|
|
412
|
+
|
|
413
|
+
# Write filter items
|
|
414
|
+
for nm in range(filtered_dict_items_cnt):
|
|
415
|
+
sw.extend(bytes([0x00, 0x00, 0x00, 0x00]))
|
|
416
|
+
sw.extend(bytes([self._filtered_data_list[nm].sheet_index, 0x00, 0x00, 0x00]))
|
|
417
|
+
sw.extend(bytes([self._filtered_data_list[nm].sheet_index, 0x00, 0x00, 0x00]))
|
|
418
|
+
|
|
419
|
+
sw.extend(bytes([0xE2, 0x02, 0x00]))
|
|
420
|
+
|
|
421
|
+
# Write sheet data
|
|
422
|
+
for sheet_num in range(len(self._filtered_data_list)):
|
|
423
|
+
start_column = self._filtered_data_list[sheet_num].start_column
|
|
424
|
+
end_column = self._filtered_data_list[sheet_num].end_column
|
|
425
|
+
start_row = self._filtered_data_list[sheet_num].start_row
|
|
426
|
+
end_row = self._filtered_data_list[sheet_num].end_row
|
|
427
|
+
sheet_index = self._filtered_data_list[sheet_num].sheet_index
|
|
428
|
+
|
|
429
|
+
# Modify magic bytes
|
|
430
|
+
self._magicFilterExcel2016Fix1[7] = sheet_index
|
|
431
|
+
self._magicFilterExcel2016Fix1[-2] = sheet_num
|
|
432
|
+
sw.extend(self._magicFilterExcel2016Fix1)
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
# Write binary data using struct.pack for proper byte conversion
|
|
436
|
+
sw.extend(struct.pack('<I', start_row)) # Int32 little-endian
|
|
437
|
+
sw.extend(struct.pack('<I', end_row)) # Int32 little-endian
|
|
438
|
+
sw.extend(struct.pack('<H', start_column)) # Int16 little-endian
|
|
439
|
+
sw.extend(struct.pack('<H', end_column)) # Int16 little-endian
|
|
440
|
+
|
|
441
|
+
sw.extend(self._magicFilterExcel2016Fix2)
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
_autoFilterStartBytes = bytes([0xA1, 0x01, 0x10])
|
|
445
|
+
_sheet1Bytes = bytes([
|
|
446
|
+
0x81,0x01,0x00,0x93,0x01,0x17,0xCB,0x04,0x02,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,
|
|
447
|
+
0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x94,0x01,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
448
|
+
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x85,0x01,0x00,0x89,0x01,0x1E,0xDC,0x03,0x00,0x00,0x00,0x00,
|
|
449
|
+
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x64,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
450
|
+
0x00,0x00,0x00,0x00,0x98,0x01,0x24,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
451
|
+
0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
452
|
+
0x00,0x00,0x00,0x8A,0x01,0x00,0x86,0x01,0x00,0x25,0x06,0x01,0x00,0x02,0x0E,0x00,0x80,0x95,0x08,0x02,
|
|
453
|
+
0x05,0x00,0x26,0x00,0xE5,0x03,0x0C,0xFF,0xFF,0xFF,0xFF,0x08,0x00,0x2C,0x01,0x00,0x00,0x00,0x00,0x91,
|
|
454
|
+
0x01,0x00,0x25,0x06,0x01,0x00,0x02,0x0E,0x00,0x80,0x80,0x08,0x02,0x05,0x00,0x26,0x00,0x00,0x19,0x00,
|
|
455
|
+
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x2C,0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
456
|
+
0x00,0x00,0x00,0x00,0x07,0x0C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x92,0x01,
|
|
457
|
+
0x00,0x97,0x04,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,
|
|
458
|
+
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
459
|
+
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
460
|
+
0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0xDD,0x03,0x02,0x10,0x00,0xDC,0x03,0x30,0x66,0x66,
|
|
461
|
+
0x66,0x66,0x66,0x66,0xE6,0x3F,0x66,0x66,0x66,0x66,0x66,0x66,0xE6,0x3F,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
462
|
+
0xE8,0x3F,0x00,0x00,0x00,0x00,0x00,0x00,0xE8,0x3F,0x33,0x33,0x33,0x33,0x33,0x33,0xD3,0x3F,0x33,0x33,
|
|
463
|
+
0x33,0x33,0x33,0x33,0xD3,0x3F,0x25,0x06,0x01,0x00,0x00,0x10,0x00,0x80,0x80,0x18,0x10,0x00,0x00,0x00,
|
|
464
|
+
0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x26,0x00,0x82,0x01,0x00
|
|
465
|
+
])
|
|
466
|
+
_stylesBin = bytes([
|
|
467
|
+
0x96,0x02,0x00,0xE7,0x04,0x04,0x02,
|
|
468
|
+
0x00,0x00,0x00,0x2C,0x2C,0xA4,0x00,0x13,
|
|
469
|
+
0x00,0x00,0x00,0x79,0x00,0x79,0x00,0x79,
|
|
470
|
+
0x00,0x79,0x00,0x5C,0x00,0x2D,0x00,0x6D,
|
|
471
|
+
0x00,0x6D,0x00,0x5C,0x00,0x2D,0x00,0x64,
|
|
472
|
+
0x00,0x64,0x00,0x5C,0x00,0x20,0x00,0x68,
|
|
473
|
+
0x00,0x68,0x00,0x3A,0x00,0x6D,0x00,0x6D,
|
|
474
|
+
0x00,0x2C,0x1E,0xA6,0x00,0x0C,0x00,0x00,
|
|
475
|
+
0x00,0x79,0x00,0x79,0x00,0x79,0x00,0x79,
|
|
476
|
+
0x00,0x5C,0x00,0x2D,0x00,0x6D,0x00,0x6D,
|
|
477
|
+
0x00,0x5C,0x00,0x2D,0x00,0x64,0x00,0x64,
|
|
478
|
+
0x00,0xE8,0x04,0x00,0xE3,0x04,0x04,0x01,
|
|
479
|
+
0x00,0x00,0x00,
|
|
480
|
+
0x2B,0x27,0xDC,0x00,0x00,0x00,0x90,0x01,
|
|
481
|
+
0x00,0x00,0x00,0x02,0x00,0x00,0x07,0x01,
|
|
482
|
+
0x00,0x00,0x00,0x00,0x00,0xFF,0x02,0x07,
|
|
483
|
+
0x00,0x00,0x00,0x43,0x00,0x61,0x00,0x6C,
|
|
484
|
+
0x00,0x69,0x00,0x62,0x00,0x72,0x00,0x69,
|
|
485
|
+
0x00,
|
|
486
|
+
0x2B,0x27,0xDC,0x00,0x01,0x00,0xBC,0x02,
|
|
487
|
+
0x00,0x00,0x00,0x02,0xEE,0x00,0x07,0x01,
|
|
488
|
+
0x00,0x00,0x00,0x00,0x00,0xFF,0x02,0x07,
|
|
489
|
+
0x00,0x00,0x00,0x43,0x00,0x61,0x00,0x6C,
|
|
490
|
+
0x00,0x69,0x00,0x62,0x00,0x72,0x00,0x69,
|
|
491
|
+
0x00,
|
|
492
|
+
0x25,0x06,0x01,0x00,0x02,0x0E,0x00,0x80,0x81,0x08,0x00,0x26,0x00,0xE4,0x04,0x00,0xDB,0x04,0x04,0x02,0x00,0x00,0x00,
|
|
493
|
+
0x2D,0x44,0x00,0x00,0x00,0x00,0x03,0x40,0x00,0x00,0x00,0x00,0x00,0xFF,0x03,0x41,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x00,
|
|
494
|
+
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
495
|
+
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
496
|
+
0x00,0x2D,0x44,0x11,0x00,0x00,0x00,0x03,0x40,0x00,0x00,0x00,0x00,0x00,0xFF,0x03,0x41,0x00,0x00,0xFF,0xFF,0xFF,0xFF,
|
|
497
|
+
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
498
|
+
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
499
|
+
0x00,0x00,0xDC,0x04,0x00,0xE5,0x04,0x04,0x01,0x00,0x00,0x00,0x2E,0x33,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,
|
|
500
|
+
0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
501
|
+
0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE6,0x04,0x00,0xF2,
|
|
502
|
+
0x04,0x04,0x01,0x00,0x00,0x00,0x2F,0x10,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x10,0x00,
|
|
503
|
+
0x00,0xF3,0x04,0x00,0xE9,0x04,0x04,
|
|
504
|
+
0x04,
|
|
505
|
+
0x00,0x00,0x00,
|
|
506
|
+
0x2F,0x10,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x10,0x00,0x00,
|
|
507
|
+
0x2F,0x10,0x00,0x00,0xA4,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x10,0x01,0x00,
|
|
508
|
+
0x2F,0x10,0x00,0x00,0xA6,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x10,0x01,0x00,
|
|
509
|
+
0x2F,0x10,0x00,0x00,0x00,0x01, 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x10,0x00,0x00,
|
|
510
|
+
0xEA,0x04,0x00,0xEB,0x04,0x04,0x01,0x00,
|
|
511
|
+
0x00,0x00,0x25,0x06,0x01,0x00,0x02,0x11,0x00,0x80,0x80,0x18,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
512
|
+
0x00,0x00,0x00,0x00,0x00,0x00,0x26,0x00,0x30,0x1C,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x4E,
|
|
513
|
+
0x00,0x6F,0x00,0x72,0x00,0x6D,0x00,0x61,0x00,0x6C,0x00,0x6E,0x00,0x79,0x00,0xEC,0x04,0x00,0xF9,0x03,0x04,0x00,0x00,
|
|
514
|
+
0x00,0x00,0xFA,0x03,0x00,0xFC,0x03,0x50,0x00,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0x54,0x00,0x61,0x00,0x62,0x00,0x6C,
|
|
515
|
+
0x00,0x65,0x00,0x53,0x00,0x74,0x00,0x79,0x00,0x6C,0x00,0x65,0x00,0x4D,0x00,0x65,0x00,0x64,0x00,0x69,0x00,0x75,0x00,
|
|
516
|
+
0x6D,0x00,0x32,0x00,0x11,0x00,0x00,0x00,0x50,0x00,0x69,0x00,0x76,0x00,0x6F,0x00,0x74,0x00,0x53,0x00,0x74,0x00,0x79,
|
|
517
|
+
0x00,0x6C,0x00,0x65,0x00,0x4C,0x00,0x69,0x00,0x67,0x00,0x68,0x00,0x74,0x00,0x31,0x00,0x36,0x00,0xFD,0x03,0x00,0x23,
|
|
518
|
+
0x04,0x02,0x0E,0x00,0x00,0xEB,0x08,0x00,0xF6,0x08,0x2A,0x00,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0x53,0x00,0x6C,0x00,
|
|
519
|
+
0x69,0x00,0x63,0x00,0x65,0x00,0x72,0x00,0x53,0x00,0x74,0x00,0x79,0x00,0x6C,0x00,0x65,0x00,0x4C,0x00,0x69,0x00,0x67,
|
|
520
|
+
0x00,0x68,0x00,0x74,0x00,0x31,0x00,0xF7,0x08,0x00,0xEC,0x08,0x00,0x24,0x00,0x23,0x04,0x03,0x0F,0x00,0x00,0xB0,0x10,
|
|
521
|
+
0x00,0xB2,0x10,0x32,0x00,0x00,0x00,0x00,0x15,0x00,0x00,0x00,0x54,0x00,0x69,0x00,0x6D,0x00,0x65,0x00,0x53,0x00,0x6C,
|
|
522
|
+
0x00,0x69,0x00,0x63,0x00,0x65,0x00,0x72,0x00,0x53,0x00,0x74,0x00,0x79,0x00,0x6C,0x00,0x65,0x00,0x4C,0x00,0x69,0x00,
|
|
523
|
+
0x67,0x00,0x68,0x00,0x74,0x00,0x31,0x00,0xB3,0x10,0x00,0xB1,0x10,0x00,0x24,0x00,0x97,0x02,0x00
|
|
524
|
+
])
|
|
525
|
+
_workbookBinStart = bytes([
|
|
526
|
+
0x83,0x01,0x00,0x80,0x01,0x32,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
527
|
+
0x00,0x00,0x02,0x00,0x00,0x00,0x78,0x00,0x6C,0x00,0x01,0x00,0x00,0x00,0x37,0x00,0x01,0x00,0x00,0x00,
|
|
528
|
+
0x36,0x00,0x05,0x00,0x00,0x00,0x32,0x00,0x34,0x00,0x33,0x00,0x32,0x00,0x36,0x00,0x99,0x01,0x0C,0x20,
|
|
529
|
+
0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x25,0x06,0x01,0x00,0x03,0x0F,0x00,0x80,0x97,
|
|
530
|
+
0x10,0x34,0x18,0x00,0x00,0x00,0x43,0x00,0x3A,0x00,0x5C,0x00,0x73,0x00,0x71,0x00,0x6C,0x00,0x73,0x00,
|
|
531
|
+
0x5C,0x00,0x54,0x00,0x65,0x00,0x73,0x00,0x74,0x00,0x79,0x00,0x5A,0x00,0x61,0x00,0x70,0x00,0x69,0x00,
|
|
532
|
+
0x73,0x00,0x75,0x00,0x58,0x00,0x6C,0x00,0x73,0x00,0x62,0x00,0x5C,0x00,0x26,0x00,0x25,0x06,0x01,0x00,
|
|
533
|
+
0x00,0x10,0x00,0x80,0x81,0x18,0x82,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x2F,0x00,0x00,0x00,
|
|
534
|
+
0x31,0x00,0x33,0x00,0x5F,0x00,0x6E,0x00,0x63,0x00,0x72,0x00,0x3A,0x00,0x31,0x00,0x5F,0x00,0x7B,0x00,
|
|
535
|
+
0x31,0x00,0x36,0x00,0x35,0x00,0x30,0x00,0x38,0x00,0x44,0x00,0x36,0x00,0x39,0x00,0x2D,0x00,0x43,0x00,
|
|
536
|
+
0x46,0x00,0x38,0x00,0x37,0x00,0x2D,0x00,0x34,0x00,0x37,0x00,0x36,0x00,0x39,0x00,0x2D,0x00,0x38,0x00,
|
|
537
|
+
0x34,0x00,0x35,0x00,0x36,0x00,0x2D,0x00,0x44,0x00,0x34,0x00,0x41,0x00,0x34,0x00,0x30,0x00,0x31,0x00,
|
|
538
|
+
0x31,0x00,0x33,0x00,0x31,0x00,0x35,0x00,0x36,0x00,0x37,0x00,0x7D,0x00,0x2F,0x00,0x00,0x00,0x2F,0x00,
|
|
539
|
+
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x26,0x00,
|
|
540
|
+
0x87,0x01,0x00,0x25,0x06,0x01,0x00,0x02,0x10,0x00,0x80,0x80,0x18,0x10,0x00,0x00,0x00,0x00,0x0D,0x00,
|
|
541
|
+
0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x26,0x00,0x9E,0x01,0x1D,0x00,0x00,0x00,0x00,0x9E,
|
|
542
|
+
0x16,0x00,0x00,0xB4,0x69,0x00,0x00,0xE8,0x26,0x00,0x00,0x58,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
543
|
+
0x00,0x00,0x00,0x78,0x88,0x01,0x00,0x8F,0x01,0x00
|
|
544
|
+
])
|
|
545
|
+
_workbookBinMiddle = bytes([0x90,0x01,0x00])
|
|
546
|
+
_workbookBinEnd = bytes([
|
|
547
|
+
0x9D,0x01,0x1A,0x35,0xEA,0x02,0x00,0x01,0x00,0x00,0x00,0x64,0x00,0x00,0x00,0xFC,0xA9,0xF1,0xD2,0x4D,
|
|
548
|
+
0x62,0x50,0x3F,0x01,0x00,0x00,0x00,0x6A,0x00,0x9B,0x01,0x01,0x00,0x23,0x04,0x03,0x0F,0x00,0x00,0xAB,
|
|
549
|
+
0x10,0x01,0x01,0x24,0x00,0x84,0x01,0x00
|
|
550
|
+
])
|
|
551
|
+
_binaryIndexBin = bytes([
|
|
552
|
+
0x2A,0x18,0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
553
|
+
0x00,0x00,0x00,0x00,0x00,0x00,0x95,0x02,0x00
|
|
554
|
+
])
|
|
555
|
+
_stickHeaderA1bytes =bytes([
|
|
556
|
+
0x97,0x01,0x1D,0x00,0x00,0x00,0x00,0x00,
|
|
557
|
+
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
558
|
+
0x00,0xF0,0x3F,0x01,0x00,0x00,0x00,0x00,
|
|
559
|
+
0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x03,
|
|
560
|
+
])
|
|
561
|
+
_autoFilterEndBytes = bytes([0xA2, 0x01, 0x00])
|
|
562
|
+
_magicFilterExcel2016Fix0 = bytes([0xE1, 0x02, 0x00, 0xE5, 0x02, 0x00, 0xEA, 0x02])
|
|
563
|
+
_magicFilterExcel2016Fix1 = bytearray([
|
|
564
|
+
0x27,
|
|
565
|
+
0x46,
|
|
566
|
+
0x21,
|
|
567
|
+
0x00,
|
|
568
|
+
0x00,
|
|
569
|
+
0x00,
|
|
570
|
+
0x00,
|
|
571
|
+
255,# -> (byte)sheetIndex,
|
|
572
|
+
0x00,
|
|
573
|
+
0x00,
|
|
574
|
+
0x00,
|
|
575
|
+
0x0F,
|
|
576
|
+
0x00,
|
|
577
|
+
0x00,
|
|
578
|
+
0x00,
|
|
579
|
+
0x5F,
|
|
580
|
+
0x00, # _0, // FilterDatabase (UTF16) - starts
|
|
581
|
+
0x46,
|
|
582
|
+
0x00,
|
|
583
|
+
0x69,
|
|
584
|
+
0x00,
|
|
585
|
+
0x6C,
|
|
586
|
+
0x00,
|
|
587
|
+
0x74,
|
|
588
|
+
0x00,
|
|
589
|
+
0x65,
|
|
590
|
+
0x00,
|
|
591
|
+
0x72,
|
|
592
|
+
0x00,
|
|
593
|
+
0x44,
|
|
594
|
+
0x00,
|
|
595
|
+
0x61,
|
|
596
|
+
0x00,
|
|
597
|
+
0x74,
|
|
598
|
+
0x00,
|
|
599
|
+
0x61,
|
|
600
|
+
0x00,
|
|
601
|
+
0x62,
|
|
602
|
+
0x00,
|
|
603
|
+
0x61,
|
|
604
|
+
0x00,
|
|
605
|
+
0x73,
|
|
606
|
+
0x00,
|
|
607
|
+
0x65,
|
|
608
|
+
0x00,# FilterDatabase (UTF16) - ends
|
|
609
|
+
0x0F,
|
|
610
|
+
0x00,
|
|
611
|
+
0x00,
|
|
612
|
+
0x00,
|
|
613
|
+
0x3B,
|
|
614
|
+
255,#->(byte)sheetNum,
|
|
615
|
+
0x00
|
|
616
|
+
])
|
|
617
|
+
_magicFilterExcel2016Fix2 = bytes([0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF])
|
|
618
|
+
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyxlsbwriter
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A Python library to write XLSB files.
|
|
5
|
+
Author: Krzysztof Duśko
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: xlsxwriter
|
|
12
|
+
Provides-Extra: test
|
|
13
|
+
Requires-Dist: pyodbc; extra == "test"
|
|
14
|
+
Provides-Extra: examples
|
|
15
|
+
Requires-Dist: memory-profiler; extra == "examples"
|
|
16
|
+
|
|
17
|
+
# Python XLSB Writer
|
|
18
|
+
|
|
19
|
+
A Python library for writing large data sets to XLSB files efficiently.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install pyxlsbwriter
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from pyxlsbwriter import XlsbWriter
|
|
31
|
+
import datetime
|
|
32
|
+
from decimal import Decimal
|
|
33
|
+
|
|
34
|
+
data = [
|
|
35
|
+
["Name", "Age", "City", "info"],
|
|
36
|
+
[-123, 2147483647, 2147483648, 2147483999],
|
|
37
|
+
["x", "y", "z", datetime.datetime.today()],
|
|
38
|
+
["Alice", 25, "New York", datetime.date.today()],
|
|
39
|
+
["Bob", 30, "London", Decimal(3.14)],
|
|
40
|
+
["Charlie", 35, "Paris", datetime.datetime.now()],
|
|
41
|
+
[True, False, None, datetime.datetime.utcnow()]
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
writer = XlsbWriter("test.xlsb")
|
|
45
|
+
writer.add_sheet("Sheet1")
|
|
46
|
+
writer.write_sheet(data)
|
|
47
|
+
writer.save()
|
|
48
|
+
```
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/pyxlsbwriter/__init__.py
|
|
4
|
+
src/pyxlsbwriter/writer.py
|
|
5
|
+
src/pyxlsbwriter.egg-info/PKG-INFO
|
|
6
|
+
src/pyxlsbwriter.egg-info/SOURCES.txt
|
|
7
|
+
src/pyxlsbwriter.egg-info/dependency_links.txt
|
|
8
|
+
src/pyxlsbwriter.egg-info/requires.txt
|
|
9
|
+
src/pyxlsbwriter.egg-info/top_level.txt
|
|
10
|
+
tests/test_real_data.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pyxlsbwriter
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import Generator
|
|
3
|
+
import pyodbc
|
|
4
|
+
from pyxlsbwriter import XlsbWriter
|
|
5
|
+
|
|
6
|
+
# --- CONFIGURATION ---
|
|
7
|
+
# CHANGE THE VALUES BELOW TO MATCH YOUR NETEZZA DATABASE
|
|
8
|
+
# Make sure you have an ODBC data source (DSN) configured for Netezza on your system.
|
|
9
|
+
password = os.environ.get("NZ_DEV_PASSWORD", "password")
|
|
10
|
+
DSN = f"DRIVER={{NetezzaSQL}};SERVER=linux.local;PORT=5480;DATABASE=JUST_DATA;UID=admin;PWD={password};"
|
|
11
|
+
# Sample query. Change it to one that works on your database.
|
|
12
|
+
QUERY1 = "SELECT * FROM JUST_DATA..DIMACCOUNT"
|
|
13
|
+
QUERY2 = "SELECT * FROM JUST_DATA..DIMCURRENCY"
|
|
14
|
+
OUTPUT_FILENAME = "real_netezza_output.xlsb"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def row_generator(cursor: pyodbc.Cursor) -> Generator[list[any], None, None]:
|
|
18
|
+
"""
|
|
19
|
+
Generates rows from a pyodbc cursor, yielding headers first, followed by data rows.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
cursor: A pyodbc cursor object containing query results.
|
|
23
|
+
|
|
24
|
+
Yields:
|
|
25
|
+
List[Any]: First yields the list of column headers, then each row as a list.
|
|
26
|
+
|
|
27
|
+
Raises:
|
|
28
|
+
pyodbc.Error: If there's an error accessing the cursor's data.
|
|
29
|
+
"""
|
|
30
|
+
try:
|
|
31
|
+
# Extract column headers from cursor description
|
|
32
|
+
headers = [column[0] for column in cursor.description]
|
|
33
|
+
yield headers
|
|
34
|
+
|
|
35
|
+
# Yield each row until the cursor is exhausted
|
|
36
|
+
while row := cursor.fetchone():
|
|
37
|
+
yield list(row)
|
|
38
|
+
except pyodbc.Error as e:
|
|
39
|
+
raise pyodbc.Error(f"Error processing cursor data: {e}")
|
|
40
|
+
|
|
41
|
+
def main():
|
|
42
|
+
"""
|
|
43
|
+
Tests fetching data from a real Netezza database and writing to an XLSB file.
|
|
44
|
+
"""
|
|
45
|
+
if os.path.exists(OUTPUT_FILENAME):
|
|
46
|
+
os.remove(OUTPUT_FILENAME)
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
print(f"Connecting to the database using DSN: '{DSN.split(';')[0]}...'")
|
|
50
|
+
with pyodbc.connect(DSN) as conn:
|
|
51
|
+
cursor = conn.cursor()
|
|
52
|
+
print(f"Executing query: '{QUERY1}'")
|
|
53
|
+
cursor.execute(QUERY1)
|
|
54
|
+
|
|
55
|
+
# Use XlsbWriter to write the data
|
|
56
|
+
print(f"Writing data to file '{OUTPUT_FILENAME}'...")
|
|
57
|
+
writer = XlsbWriter(OUTPUT_FILENAME)
|
|
58
|
+
writer.add_sheet("Netezza Export")
|
|
59
|
+
writer.write_sheet(row_generator(cursor))
|
|
60
|
+
|
|
61
|
+
writer.add_sheet("query 2")
|
|
62
|
+
print(f"Executing query: '{QUERY2}'")
|
|
63
|
+
cursor.execute(QUERY2)
|
|
64
|
+
writer.write_sheet(row_generator(cursor))
|
|
65
|
+
writer.add_sheet("SQL1")
|
|
66
|
+
writer.write_sheet([["code"],[QUERY1]] ,hidden=True)
|
|
67
|
+
writer.add_sheet("SQL2")
|
|
68
|
+
writer.write_sheet([["code"],[QUERY2]] ,hidden=True)
|
|
69
|
+
writer.save()
|
|
70
|
+
|
|
71
|
+
except pyodbc.Error as ex:
|
|
72
|
+
sqlstate = ex.args[0]
|
|
73
|
+
print(f"Database connection or query execution error: {sqlstate}\n{ex}")
|
|
74
|
+
except Exception as e:
|
|
75
|
+
print(f"An unexpected error occurred: {e}")
|
|
76
|
+
|
|
77
|
+
# Check if the XLSB file was created and is not empty
|
|
78
|
+
if os.path.exists(OUTPUT_FILENAME) and os.path.getsize(OUTPUT_FILENAME) > 0:
|
|
79
|
+
print(f"\nTest completed successfully, file '{OUTPUT_FILENAME}' was created and contains data.")
|
|
80
|
+
else:
|
|
81
|
+
print(f"\nTest failed, file '{OUTPUT_FILENAME}' was not created or is empty.")
|
|
82
|
+
|
|
83
|
+
if os.path.exists(OUTPUT_FILENAME):
|
|
84
|
+
os.remove(OUTPUT_FILENAME)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
if __name__ == '__main__':
|
|
88
|
+
# Checking if pyodbc is installed
|
|
89
|
+
try:
|
|
90
|
+
import pyodbc
|
|
91
|
+
main()
|
|
92
|
+
except ImportError:
|
|
93
|
+
print("pyodbc library is not installed. Installing...")
|
|
94
|
+
import subprocess
|
|
95
|
+
import sys
|
|
96
|
+
try:
|
|
97
|
+
subprocess.check_call([sys.executable, "-m", "pip", "install", "pyodbc"])
|
|
98
|
+
main()
|
|
99
|
+
except subprocess.CalledProcessError as e:
|
|
100
|
+
print(f"Failed to install pyodbc. Please install it manually: pip install pyodbc")
|
|
101
|
+
sys.exit(1)
|
|
102
|
+
|