aspose-cells-foss 25.12.1__py3-none-any.whl → 26.2.2__py3-none-any.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.
- aspose_cells/__init__.py +88 -0
- aspose_cells/auto_filter.py +527 -0
- aspose_cells/cell.py +483 -0
- aspose_cells/cell_value_handler.py +319 -0
- aspose_cells/cells.py +779 -0
- aspose_cells/cfb_handler.py +445 -0
- aspose_cells/cfb_writer.py +659 -0
- aspose_cells/cfb_writer_minimal.py +337 -0
- aspose_cells/comment_xml.py +475 -0
- aspose_cells/conditional_format.py +1185 -0
- aspose_cells/csv_handler.py +690 -0
- aspose_cells/data_validation.py +911 -0
- aspose_cells/document_properties.py +356 -0
- aspose_cells/encryption_crypto.py +247 -0
- aspose_cells/encryption_params.py +138 -0
- aspose_cells/hyperlink.py +372 -0
- aspose_cells/json_handler.py +185 -0
- aspose_cells/markdown_handler.py +583 -0
- aspose_cells/shared_strings.py +101 -0
- aspose_cells/style.py +841 -0
- aspose_cells/workbook.py +499 -0
- aspose_cells/workbook_hash_password.py +68 -0
- aspose_cells/workbook_properties.py +712 -0
- aspose_cells/worksheet.py +570 -0
- aspose_cells/worksheet_properties.py +1239 -0
- aspose_cells/xlsx_encryptor.py +403 -0
- aspose_cells/xml_autofilter_loader.py +195 -0
- aspose_cells/xml_autofilter_saver.py +173 -0
- aspose_cells/xml_conditional_format_loader.py +215 -0
- aspose_cells/xml_conditional_format_saver.py +351 -0
- aspose_cells/xml_datavalidation_loader.py +239 -0
- aspose_cells/xml_datavalidation_saver.py +245 -0
- aspose_cells/xml_hyperlink_handler.py +323 -0
- aspose_cells/xml_loader.py +986 -0
- aspose_cells/xml_properties_loader.py +512 -0
- aspose_cells/xml_properties_saver.py +607 -0
- aspose_cells/xml_saver.py +1306 -0
- aspose_cells_foss-26.2.2.dist-info/METADATA +190 -0
- aspose_cells_foss-26.2.2.dist-info/RECORD +41 -0
- {aspose_cells_foss-25.12.1.dist-info → aspose_cells_foss-26.2.2.dist-info}/WHEEL +1 -1
- aspose_cells_foss-26.2.2.dist-info/top_level.txt +1 -0
- aspose/__init__.py +0 -14
- aspose/cells/__init__.py +0 -31
- aspose/cells/cell.py +0 -350
- aspose/cells/constants.py +0 -44
- aspose/cells/converters/__init__.py +0 -13
- aspose/cells/converters/csv_converter.py +0 -55
- aspose/cells/converters/json_converter.py +0 -46
- aspose/cells/converters/markdown_converter.py +0 -453
- aspose/cells/drawing/__init__.py +0 -17
- aspose/cells/drawing/anchor.py +0 -172
- aspose/cells/drawing/collection.py +0 -233
- aspose/cells/drawing/image.py +0 -338
- aspose/cells/formats.py +0 -80
- aspose/cells/formula/__init__.py +0 -10
- aspose/cells/formula/evaluator.py +0 -360
- aspose/cells/formula/functions.py +0 -433
- aspose/cells/formula/tokenizer.py +0 -340
- aspose/cells/io/__init__.py +0 -27
- aspose/cells/io/csv/__init__.py +0 -8
- aspose/cells/io/csv/reader.py +0 -88
- aspose/cells/io/csv/writer.py +0 -98
- aspose/cells/io/factory.py +0 -138
- aspose/cells/io/interfaces.py +0 -48
- aspose/cells/io/json/__init__.py +0 -8
- aspose/cells/io/json/reader.py +0 -126
- aspose/cells/io/json/writer.py +0 -119
- aspose/cells/io/md/__init__.py +0 -8
- aspose/cells/io/md/reader.py +0 -161
- aspose/cells/io/md/writer.py +0 -334
- aspose/cells/io/models.py +0 -64
- aspose/cells/io/xlsx/__init__.py +0 -9
- aspose/cells/io/xlsx/constants.py +0 -312
- aspose/cells/io/xlsx/image_writer.py +0 -311
- aspose/cells/io/xlsx/reader.py +0 -284
- aspose/cells/io/xlsx/writer.py +0 -931
- aspose/cells/plugins/__init__.py +0 -6
- aspose/cells/plugins/docling_backend/__init__.py +0 -7
- aspose/cells/plugins/docling_backend/backend.py +0 -535
- aspose/cells/plugins/markitdown_plugin/__init__.py +0 -15
- aspose/cells/plugins/markitdown_plugin/plugin.py +0 -128
- aspose/cells/range.py +0 -210
- aspose/cells/style.py +0 -287
- aspose/cells/utils/__init__.py +0 -54
- aspose/cells/utils/coordinates.py +0 -68
- aspose/cells/utils/exceptions.py +0 -43
- aspose/cells/utils/validation.py +0 -102
- aspose/cells/workbook.py +0 -352
- aspose/cells/worksheet.py +0 -670
- aspose_cells_foss-25.12.1.dist-info/METADATA +0 -189
- aspose_cells_foss-25.12.1.dist-info/RECORD +0 -53
- aspose_cells_foss-25.12.1.dist-info/entry_points.txt +0 -2
- aspose_cells_foss-25.12.1.dist-info/top_level.txt +0 -1
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Minimal CFB (Compound File Binary) Writer
|
|
3
|
+
|
|
4
|
+
This module provides a minimal CFB file writer for creating encrypted XLSX files.
|
|
5
|
+
CFB format is also known as OLE (Object Linking and Embedding) format.
|
|
6
|
+
|
|
7
|
+
Based on MS-CFB specification.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import struct
|
|
11
|
+
import io
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MinimalCFBWriter:
|
|
15
|
+
"""
|
|
16
|
+
Minimal CFB file writer for encrypted Office documents.
|
|
17
|
+
|
|
18
|
+
This implementation creates a simple CFB file with just two streams:
|
|
19
|
+
- EncryptionInfo
|
|
20
|
+
- EncryptedPackage
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
# CFB Constants
|
|
24
|
+
HEADER_SIGNATURE = 0xE11AB1A1E011CFD0
|
|
25
|
+
MINOR_VERSION = 0x003E
|
|
26
|
+
MAJOR_VERSION_3 = 0x0003 # Version 3 (512-byte sectors)
|
|
27
|
+
MAJOR_VERSION_4 = 0x0004 # Version 4 (4096-byte sectors)
|
|
28
|
+
BYTE_ORDER = 0xFFFE
|
|
29
|
+
SECTOR_SIZE_V3 = 512
|
|
30
|
+
SECTOR_SIZE_V4 = 4096
|
|
31
|
+
MINI_SECTOR_SIZE = 64
|
|
32
|
+
DIFSECT = 0xFFFFFFFC
|
|
33
|
+
FATSECT = 0xFFFFFFFD
|
|
34
|
+
ENDOFCHAIN = 0xFFFFFFFE
|
|
35
|
+
FREESECT = 0xFFFFFFFF
|
|
36
|
+
|
|
37
|
+
def __init__(self, sector_size=512):
|
|
38
|
+
"""
|
|
39
|
+
Initialize CFB writer.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
sector_size: Sector size (512 or 4096 bytes)
|
|
43
|
+
"""
|
|
44
|
+
self.sector_size = sector_size
|
|
45
|
+
self.major_version = self.MAJOR_VERSION_3 if sector_size == 512 else self.MAJOR_VERSION_4
|
|
46
|
+
self.streams = []
|
|
47
|
+
|
|
48
|
+
def add_stream(self, name, data):
|
|
49
|
+
"""
|
|
50
|
+
Add a stream to the CFB file.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
name: Stream name
|
|
54
|
+
data: Stream data bytes
|
|
55
|
+
"""
|
|
56
|
+
self.streams.append((name, data))
|
|
57
|
+
|
|
58
|
+
def write(self, file_path):
|
|
59
|
+
"""
|
|
60
|
+
Write CFB file.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
file_path: Output file path
|
|
64
|
+
"""
|
|
65
|
+
# Build CFB structure
|
|
66
|
+
header = self._build_header()
|
|
67
|
+
fat = self._build_fat()
|
|
68
|
+
directory = self._build_directory()
|
|
69
|
+
data_sectors = self._build_data_sectors()
|
|
70
|
+
|
|
71
|
+
# Write to file
|
|
72
|
+
with open(file_path, 'wb') as f:
|
|
73
|
+
f.write(header)
|
|
74
|
+
f.write(fat)
|
|
75
|
+
f.write(directory)
|
|
76
|
+
f.write(data_sectors)
|
|
77
|
+
|
|
78
|
+
def _build_header(self):
|
|
79
|
+
"""Build CFB header (512 bytes)."""
|
|
80
|
+
header = io.BytesIO()
|
|
81
|
+
|
|
82
|
+
# Signature (8 bytes)
|
|
83
|
+
header.write(struct.pack('<Q', self.HEADER_SIGNATURE))
|
|
84
|
+
|
|
85
|
+
# CLSID (16 bytes) - all zeros
|
|
86
|
+
header.write(b'\x00' * 16)
|
|
87
|
+
|
|
88
|
+
# Minor version (2 bytes)
|
|
89
|
+
header.write(struct.pack('<H', self.MINOR_VERSION))
|
|
90
|
+
|
|
91
|
+
# Major version (2 bytes)
|
|
92
|
+
header.write(struct.pack('<H', self.major_version))
|
|
93
|
+
|
|
94
|
+
# Byte order (2 bytes)
|
|
95
|
+
header.write(struct.pack('<H', self.BYTE_ORDER))
|
|
96
|
+
|
|
97
|
+
# Sector size power (2 bytes): 9 for 512, 12 for 4096
|
|
98
|
+
sector_shift = 9 if self.sector_size == 512 else 12
|
|
99
|
+
header.write(struct.pack('<H', sector_shift))
|
|
100
|
+
|
|
101
|
+
# Mini sector size power (2 bytes): always 6 (64 bytes)
|
|
102
|
+
header.write(struct.pack('<H', 6))
|
|
103
|
+
|
|
104
|
+
# Reserved (6 bytes)
|
|
105
|
+
header.write(b'\x00' * 6)
|
|
106
|
+
|
|
107
|
+
# Total sectors (4 bytes) - 0 for version 3
|
|
108
|
+
header.write(struct.pack('<I', 0))
|
|
109
|
+
|
|
110
|
+
# FAT sectors (4 bytes)
|
|
111
|
+
fat_sectors = self._calculate_fat_sectors()
|
|
112
|
+
header.write(struct.pack('<I', fat_sectors))
|
|
113
|
+
|
|
114
|
+
# First directory sector (4 bytes)
|
|
115
|
+
header.write(struct.pack('<I', fat_sectors)) # Right after FAT
|
|
116
|
+
|
|
117
|
+
# Transaction signature (4 bytes)
|
|
118
|
+
header.write(struct.pack('<I', 0))
|
|
119
|
+
|
|
120
|
+
# Mini stream cutoff size (4 bytes) - 4096
|
|
121
|
+
header.write(struct.pack('<I', 4096))
|
|
122
|
+
|
|
123
|
+
# First mini FAT sector (4 bytes) - ENDOFCHAIN (no mini stream)
|
|
124
|
+
header.write(struct.pack('<I', self.ENDOFCHAIN))
|
|
125
|
+
|
|
126
|
+
# Number of mini FAT sectors (4 bytes)
|
|
127
|
+
header.write(struct.pack('<I', 0))
|
|
128
|
+
|
|
129
|
+
# First DIFAT sector (4 bytes) - ENDOFCHAIN
|
|
130
|
+
header.write(struct.pack('<I', self.ENDOFCHAIN))
|
|
131
|
+
|
|
132
|
+
# Number of DIFAT sectors (4 bytes)
|
|
133
|
+
header.write(struct.pack('<I', 0))
|
|
134
|
+
|
|
135
|
+
# DIFAT array (436 bytes for 109 entries)
|
|
136
|
+
# First FAT sector is at sector 0
|
|
137
|
+
for i in range(109):
|
|
138
|
+
if i == 0:
|
|
139
|
+
header.write(struct.pack('<I', 0)) # FAT at sector 0
|
|
140
|
+
else:
|
|
141
|
+
header.write(struct.pack('<I', self.FREESECT))
|
|
142
|
+
|
|
143
|
+
# Pad to 512 bytes
|
|
144
|
+
data = header.getvalue()
|
|
145
|
+
if len(data) < 512:
|
|
146
|
+
data += b'\x00' * (512 - len(data))
|
|
147
|
+
|
|
148
|
+
return data[:512]
|
|
149
|
+
|
|
150
|
+
def _calculate_fat_sectors(self):
|
|
151
|
+
"""Calculate number of FAT sectors needed."""
|
|
152
|
+
# We need:
|
|
153
|
+
# - 1 FAT sector
|
|
154
|
+
# - 1 directory sector
|
|
155
|
+
# - N data sectors for streams
|
|
156
|
+
|
|
157
|
+
total_data = sum(len(data) for _, data in self.streams)
|
|
158
|
+
data_sectors = (total_data + self.sector_size - 1) // self.sector_size
|
|
159
|
+
|
|
160
|
+
# For simplicity, allocate 1 FAT sector (can hold 128 entries for 512-byte sectors)
|
|
161
|
+
return 1
|
|
162
|
+
|
|
163
|
+
def _build_fat(self):
|
|
164
|
+
"""Build File Allocation Table."""
|
|
165
|
+
entries_per_sector = self.sector_size // 4
|
|
166
|
+
fat = io.BytesIO()
|
|
167
|
+
|
|
168
|
+
# FAT entries:
|
|
169
|
+
# Sector 0: FAT sector itself (FATSECT)
|
|
170
|
+
fat.write(struct.pack('<I', self.FATSECT))
|
|
171
|
+
|
|
172
|
+
# Sector 1: Directory sector (ENDOFCHAIN)
|
|
173
|
+
fat.write(struct.pack('<I', self.ENDOFCHAIN))
|
|
174
|
+
|
|
175
|
+
# Build separate chains for each stream
|
|
176
|
+
current_sector = 2
|
|
177
|
+
for stream_idx, (name, data) in enumerate(self.streams):
|
|
178
|
+
sectors_needed = (len(data) + self.sector_size - 1) // self.sector_size
|
|
179
|
+
|
|
180
|
+
# Create chain for this stream
|
|
181
|
+
for i in range(sectors_needed):
|
|
182
|
+
if i == sectors_needed - 1:
|
|
183
|
+
# Last sector of this stream
|
|
184
|
+
fat.write(struct.pack('<I', self.ENDOFCHAIN))
|
|
185
|
+
else:
|
|
186
|
+
# Point to next sector in chain
|
|
187
|
+
fat.write(struct.pack('<I', current_sector + i + 1))
|
|
188
|
+
|
|
189
|
+
current_sector += sectors_needed
|
|
190
|
+
|
|
191
|
+
# Fill rest of FAT sector with FREESECT
|
|
192
|
+
total_data_sectors = sum((len(d) + self.sector_size - 1) // self.sector_size
|
|
193
|
+
for _, d in self.streams)
|
|
194
|
+
entries_written = 2 + total_data_sectors
|
|
195
|
+
for i in range(entries_written, entries_per_sector):
|
|
196
|
+
fat.write(struct.pack('<I', self.FREESECT))
|
|
197
|
+
|
|
198
|
+
return fat.getvalue()
|
|
199
|
+
|
|
200
|
+
def _build_directory(self):
|
|
201
|
+
"""Build directory entries."""
|
|
202
|
+
directory = io.BytesIO()
|
|
203
|
+
|
|
204
|
+
# Root entry (entry 0)
|
|
205
|
+
root_entry = self._create_directory_entry(
|
|
206
|
+
name="Root Entry",
|
|
207
|
+
obj_type=5, # Root storage
|
|
208
|
+
color=1, # Black
|
|
209
|
+
left_sibling=0xFFFFFFFF,
|
|
210
|
+
right_sibling=0xFFFFFFFF,
|
|
211
|
+
child_did=1 if len(self.streams) > 0 else 0xFFFFFFFF,
|
|
212
|
+
clsid=b'\x00' * 16,
|
|
213
|
+
state_bits=0,
|
|
214
|
+
creation_time=0,
|
|
215
|
+
modified_time=0,
|
|
216
|
+
starting_sector=0xFFFFFFFE, # ENDOFCHAIN
|
|
217
|
+
stream_size=0
|
|
218
|
+
)
|
|
219
|
+
directory.write(root_entry)
|
|
220
|
+
|
|
221
|
+
# Stream entries
|
|
222
|
+
# For simplicity with 2 streams, create simple red-black tree:
|
|
223
|
+
# Stream 0 (EncryptionInfo) - entry 1, left child of root
|
|
224
|
+
# Stream 1 (EncryptedPackage) - entry 2, right sibling of stream 0
|
|
225
|
+
for i, (name, data) in enumerate(self.streams):
|
|
226
|
+
# Calculate starting sector (after FAT and directory)
|
|
227
|
+
if i == 0:
|
|
228
|
+
starting_sector = 2 # After FAT (0) and directory (1)
|
|
229
|
+
else:
|
|
230
|
+
prev_data = sum(len(d) for _, d in self.streams[:i])
|
|
231
|
+
prev_sectors = (prev_data + self.sector_size - 1) // self.sector_size
|
|
232
|
+
starting_sector = 2 + prev_sectors
|
|
233
|
+
|
|
234
|
+
# Set up tree structure for exactly 2 streams
|
|
235
|
+
if i == 0:
|
|
236
|
+
# First stream: left child of root, right sibling is second stream
|
|
237
|
+
left_sib = 0xFFFFFFFF
|
|
238
|
+
right_sib = 2 if len(self.streams) > 1 else 0xFFFFFFFF
|
|
239
|
+
elif i == 1:
|
|
240
|
+
# Second stream: right sibling of first stream
|
|
241
|
+
left_sib = 0xFFFFFFFF
|
|
242
|
+
right_sib = 0xFFFFFFFF
|
|
243
|
+
else:
|
|
244
|
+
left_sib = 0xFFFFFFFF
|
|
245
|
+
right_sib = 0xFFFFFFFF
|
|
246
|
+
|
|
247
|
+
entry = self._create_directory_entry(
|
|
248
|
+
name=name,
|
|
249
|
+
obj_type=2, # Stream
|
|
250
|
+
color=1, # Black
|
|
251
|
+
left_sibling=left_sib,
|
|
252
|
+
right_sibling=right_sib,
|
|
253
|
+
child_did=0xFFFFFFFF,
|
|
254
|
+
clsid=b'\x00' * 16,
|
|
255
|
+
state_bits=0,
|
|
256
|
+
creation_time=0,
|
|
257
|
+
modified_time=0,
|
|
258
|
+
starting_sector=starting_sector,
|
|
259
|
+
stream_size=len(data)
|
|
260
|
+
)
|
|
261
|
+
directory.write(entry)
|
|
262
|
+
|
|
263
|
+
# Fill rest of directory sector
|
|
264
|
+
entries_per_sector = self.sector_size // 128
|
|
265
|
+
entries_written = 1 + len(self.streams)
|
|
266
|
+
for i in range(entries_written, entries_per_sector):
|
|
267
|
+
directory.write(b'\xFF' * 128)
|
|
268
|
+
|
|
269
|
+
return directory.getvalue()
|
|
270
|
+
|
|
271
|
+
def _create_directory_entry(self, name, obj_type, color, left_sibling, right_sibling,
|
|
272
|
+
child_did, clsid, state_bits, creation_time, modified_time,
|
|
273
|
+
starting_sector, stream_size):
|
|
274
|
+
"""Create a directory entry (128 bytes)."""
|
|
275
|
+
entry = io.BytesIO()
|
|
276
|
+
|
|
277
|
+
# Name (64 bytes): UTF-16LE, null-terminated
|
|
278
|
+
name_bytes = name.encode('utf-16le')
|
|
279
|
+
if len(name_bytes) > 62:
|
|
280
|
+
name_bytes = name_bytes[:62]
|
|
281
|
+
entry.write(name_bytes)
|
|
282
|
+
entry.write(b'\x00' * (64 - len(name_bytes)))
|
|
283
|
+
|
|
284
|
+
# Name length (2 bytes): including null terminator
|
|
285
|
+
entry.write(struct.pack('<H', len(name_bytes) + 2))
|
|
286
|
+
|
|
287
|
+
# Object type (1 byte)
|
|
288
|
+
entry.write(struct.pack('<B', obj_type))
|
|
289
|
+
|
|
290
|
+
# Color (1 byte)
|
|
291
|
+
entry.write(struct.pack('<B', color))
|
|
292
|
+
|
|
293
|
+
# Left sibling DID (4 bytes)
|
|
294
|
+
entry.write(struct.pack('<I', left_sibling))
|
|
295
|
+
|
|
296
|
+
# Right sibling DID (4 bytes)
|
|
297
|
+
entry.write(struct.pack('<I', right_sibling))
|
|
298
|
+
|
|
299
|
+
# Child DID (4 bytes)
|
|
300
|
+
entry.write(struct.pack('<I', child_did))
|
|
301
|
+
|
|
302
|
+
# CLSID (16 bytes)
|
|
303
|
+
entry.write(clsid)
|
|
304
|
+
|
|
305
|
+
# State bits (4 bytes)
|
|
306
|
+
entry.write(struct.pack('<I', state_bits))
|
|
307
|
+
|
|
308
|
+
# Creation time (8 bytes)
|
|
309
|
+
entry.write(struct.pack('<Q', creation_time))
|
|
310
|
+
|
|
311
|
+
# Modified time (8 bytes)
|
|
312
|
+
entry.write(struct.pack('<Q', modified_time))
|
|
313
|
+
|
|
314
|
+
# Starting sector (4 bytes)
|
|
315
|
+
entry.write(struct.pack('<I', starting_sector))
|
|
316
|
+
|
|
317
|
+
# Stream size (8 bytes)
|
|
318
|
+
entry.write(struct.pack('<Q', stream_size))
|
|
319
|
+
|
|
320
|
+
data = entry.getvalue()
|
|
321
|
+
assert len(data) == 128
|
|
322
|
+
return data
|
|
323
|
+
|
|
324
|
+
def _build_data_sectors(self):
|
|
325
|
+
"""Build data sectors for all streams."""
|
|
326
|
+
data = io.BytesIO()
|
|
327
|
+
|
|
328
|
+
for name, stream_data in self.streams:
|
|
329
|
+
# Write stream data
|
|
330
|
+
data.write(stream_data)
|
|
331
|
+
|
|
332
|
+
# Pad to sector boundary
|
|
333
|
+
padding = (self.sector_size - (len(stream_data) % self.sector_size)) % self.sector_size
|
|
334
|
+
if padding > 0:
|
|
335
|
+
data.write(b'\x00' * padding)
|
|
336
|
+
|
|
337
|
+
return data.getvalue()
|