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,690 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Aspose.Cells for Python - CSV Handler Module
|
|
3
|
+
|
|
4
|
+
This module provides CSV import and export functionality for workbooks.
|
|
5
|
+
It supports reading from and writing to CSV (Comma-Separated Values) files
|
|
6
|
+
with configurable delimiters, encodings, and type inference options.
|
|
7
|
+
|
|
8
|
+
Features:
|
|
9
|
+
- Export worksheet data to CSV format
|
|
10
|
+
- Import CSV data into a new workbook
|
|
11
|
+
- Configurable delimiter (comma, semicolon, tab, etc.)
|
|
12
|
+
- Configurable encoding (UTF-8, UTF-16, Latin-1, etc.)
|
|
13
|
+
- Automatic type inference for imported values
|
|
14
|
+
- Proper handling of quoted fields, escaped characters, and multiline values
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import csv
|
|
18
|
+
import io
|
|
19
|
+
import locale
|
|
20
|
+
import re
|
|
21
|
+
from datetime import datetime, date, time
|
|
22
|
+
from typing import Optional, List, Any
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _system_encoding() -> str:
|
|
26
|
+
"""Return the system's preferred encoding (e.g. 'cp1252', 'gbk', 'utf-8')."""
|
|
27
|
+
return locale.getpreferredencoding(False) or 'utf-8'
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class CSVLoadOptions:
|
|
31
|
+
"""
|
|
32
|
+
Options for loading CSV files.
|
|
33
|
+
|
|
34
|
+
Attributes:
|
|
35
|
+
delimiter (str): Field delimiter character. Default is ','.
|
|
36
|
+
encoding (str): File encoding. Default is the system's preferred encoding.
|
|
37
|
+
has_header (bool): Whether the first row contains headers. Default is False.
|
|
38
|
+
quote_char (str): Character used for quoting fields. Default is '"'.
|
|
39
|
+
escape_char (str): Character used for escaping. Default is None (uses doubling).
|
|
40
|
+
skip_rows (int): Number of rows to skip at the beginning. Default is 0.
|
|
41
|
+
auto_detect_types (bool): Automatically detect and convert value types. Default is True.
|
|
42
|
+
date_formats (list): List of date format strings to try for parsing. Default formats included.
|
|
43
|
+
true_values (list): Values to interpret as True. Default is ['true', 'yes', '1'].
|
|
44
|
+
false_values (list): Values to interpret as False. Default is ['false', 'no', '0'].
|
|
45
|
+
|
|
46
|
+
Examples:
|
|
47
|
+
>>> options = CSVLoadOptions()
|
|
48
|
+
>>> options.delimiter = ';'
|
|
49
|
+
>>> options.encoding = 'utf-16'
|
|
50
|
+
>>> wb = Workbook()
|
|
51
|
+
>>> CSVHandler.load_csv(wb, 'data.csv', options)
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(self):
|
|
55
|
+
self.delimiter = ','
|
|
56
|
+
self.encoding = _system_encoding()
|
|
57
|
+
self.has_header = False
|
|
58
|
+
self.quote_char = '"'
|
|
59
|
+
self.escape_char = None
|
|
60
|
+
self.skip_rows = 0
|
|
61
|
+
self.auto_detect_types = True
|
|
62
|
+
self.date_formats = [
|
|
63
|
+
'%Y-%m-%d',
|
|
64
|
+
'%Y/%m/%d',
|
|
65
|
+
'%d-%m-%Y',
|
|
66
|
+
'%d/%m/%Y',
|
|
67
|
+
'%m-%d-%Y',
|
|
68
|
+
'%m/%d/%Y',
|
|
69
|
+
'%Y-%m-%d %H:%M:%S',
|
|
70
|
+
'%Y/%m/%d %H:%M:%S',
|
|
71
|
+
'%d-%m-%Y %H:%M:%S',
|
|
72
|
+
'%d/%m/%Y %H:%M:%S',
|
|
73
|
+
]
|
|
74
|
+
self.true_values = ['true', 'yes', '1', 'True', 'Yes', 'TRUE', 'YES']
|
|
75
|
+
self.false_values = ['false', 'no', '0', 'False', 'No', 'FALSE', 'NO']
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class CSVSaveOptions:
|
|
79
|
+
"""
|
|
80
|
+
Options for saving CSV files.
|
|
81
|
+
|
|
82
|
+
Attributes:
|
|
83
|
+
delimiter (str): Field delimiter character. Default is ','.
|
|
84
|
+
encoding (str): File encoding. Default is the system's preferred encoding.
|
|
85
|
+
quote_char (str): Character used for quoting fields. Default is '"'.
|
|
86
|
+
quoting (int): Quoting behavior (csv.QUOTE_MINIMAL, csv.QUOTE_ALL, etc.). Default is QUOTE_MINIMAL.
|
|
87
|
+
line_terminator (str): Line ending character(s). Default is '\\r\\n'.
|
|
88
|
+
include_header (bool): Whether to include column headers (if available). Default is False.
|
|
89
|
+
worksheet_index (int): Index of the worksheet to export. Default is 0 (first worksheet).
|
|
90
|
+
date_format (str): Format string for date values. Default is '%Y-%m-%d'.
|
|
91
|
+
datetime_format (str): Format string for datetime values. Default is '%Y-%m-%d %H:%M:%S'.
|
|
92
|
+
time_format (str): Format string for time values. Default is '%H:%M:%S'.
|
|
93
|
+
write_bom (bool): Whether to write UTF-8 BOM at file start. Default is False.
|
|
94
|
+
|
|
95
|
+
Examples:
|
|
96
|
+
>>> options = CSVSaveOptions()
|
|
97
|
+
>>> options.delimiter = '\\t'
|
|
98
|
+
>>> options.encoding = 'utf-8'
|
|
99
|
+
>>> CSVHandler.save_csv(workbook, 'output.csv', options)
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
def __init__(self):
|
|
103
|
+
self.delimiter = ','
|
|
104
|
+
self.encoding = _system_encoding()
|
|
105
|
+
self.quote_char = '"'
|
|
106
|
+
self.quoting = csv.QUOTE_MINIMAL
|
|
107
|
+
self.line_terminator = '\r\n'
|
|
108
|
+
self.include_header = False
|
|
109
|
+
self.worksheet_index = 0
|
|
110
|
+
self.date_format = '%Y-%m-%d'
|
|
111
|
+
self.datetime_format = '%Y-%m-%d %H:%M:%S'
|
|
112
|
+
self.time_format = '%H:%M:%S'
|
|
113
|
+
self.write_bom = False
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class CSVHandler:
|
|
117
|
+
"""
|
|
118
|
+
Handles CSV import and export operations for workbooks.
|
|
119
|
+
|
|
120
|
+
This class provides static methods to load CSV files into workbooks
|
|
121
|
+
and save workbook data to CSV format.
|
|
122
|
+
|
|
123
|
+
Examples:
|
|
124
|
+
# Export workbook to CSV
|
|
125
|
+
>>> wb = Workbook('data.xlsx')
|
|
126
|
+
>>> CSVHandler.save_csv(wb, 'output.csv')
|
|
127
|
+
|
|
128
|
+
# Import CSV to workbook
|
|
129
|
+
>>> wb = Workbook()
|
|
130
|
+
>>> CSVHandler.load_csv(wb, 'input.csv')
|
|
131
|
+
>>> print(wb.worksheets[0].cells['A1'].value)
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
@staticmethod
|
|
135
|
+
def save_csv(workbook, file_path: str, options: Optional[CSVSaveOptions] = None) -> None:
|
|
136
|
+
"""
|
|
137
|
+
Saves a workbook worksheet to a CSV file.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
workbook: The Workbook object to export.
|
|
141
|
+
file_path (str): Path where the CSV file should be saved.
|
|
142
|
+
options (CSVSaveOptions, optional): Export options. Uses defaults if None.
|
|
143
|
+
|
|
144
|
+
Raises:
|
|
145
|
+
IndexError: If the specified worksheet index is out of range.
|
|
146
|
+
IOError: If the file cannot be written.
|
|
147
|
+
|
|
148
|
+
Examples:
|
|
149
|
+
>>> wb = Workbook('data.xlsx')
|
|
150
|
+
>>> CSVHandler.save_csv(wb, 'output.csv')
|
|
151
|
+
|
|
152
|
+
>>> options = CSVSaveOptions()
|
|
153
|
+
>>> options.delimiter = ';'
|
|
154
|
+
>>> CSVHandler.save_csv(wb, 'output.csv', options)
|
|
155
|
+
"""
|
|
156
|
+
if options is None:
|
|
157
|
+
options = CSVSaveOptions()
|
|
158
|
+
|
|
159
|
+
# Validate worksheet index
|
|
160
|
+
if options.worksheet_index >= len(workbook.worksheets):
|
|
161
|
+
raise IndexError(f"Worksheet index {options.worksheet_index} out of range")
|
|
162
|
+
|
|
163
|
+
worksheet = workbook.worksheets[options.worksheet_index]
|
|
164
|
+
|
|
165
|
+
# Get the used range of the worksheet
|
|
166
|
+
rows_data = CSVHandler._get_worksheet_data(worksheet)
|
|
167
|
+
|
|
168
|
+
# Open file for writing
|
|
169
|
+
with open(file_path, 'w', encoding=options.encoding, newline='') as f:
|
|
170
|
+
# Write BOM if requested
|
|
171
|
+
if options.write_bom and options.encoding.lower().replace('-', '') == 'utf8':
|
|
172
|
+
f.write('\ufeff')
|
|
173
|
+
|
|
174
|
+
writer = csv.writer(
|
|
175
|
+
f,
|
|
176
|
+
delimiter=options.delimiter,
|
|
177
|
+
quotechar=options.quote_char,
|
|
178
|
+
quoting=options.quoting,
|
|
179
|
+
lineterminator=options.line_terminator
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# Write data rows
|
|
183
|
+
for row_data in rows_data:
|
|
184
|
+
formatted_row = [
|
|
185
|
+
CSVHandler._format_cell_for_csv(cell, options)
|
|
186
|
+
for cell in row_data
|
|
187
|
+
]
|
|
188
|
+
writer.writerow(formatted_row)
|
|
189
|
+
|
|
190
|
+
@staticmethod
|
|
191
|
+
def save_csv_to_string(workbook, options: Optional[CSVSaveOptions] = None) -> str:
|
|
192
|
+
"""
|
|
193
|
+
Saves a workbook worksheet to a CSV string.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
workbook: The Workbook object to export.
|
|
197
|
+
options (CSVSaveOptions, optional): Export options. Uses defaults if None.
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
str: The CSV content as a string.
|
|
201
|
+
|
|
202
|
+
Examples:
|
|
203
|
+
>>> wb = Workbook('data.xlsx')
|
|
204
|
+
>>> csv_content = CSVHandler.save_csv_to_string(wb)
|
|
205
|
+
"""
|
|
206
|
+
if options is None:
|
|
207
|
+
options = CSVSaveOptions()
|
|
208
|
+
|
|
209
|
+
# Validate worksheet index
|
|
210
|
+
if options.worksheet_index >= len(workbook.worksheets):
|
|
211
|
+
raise IndexError(f"Worksheet index {options.worksheet_index} out of range")
|
|
212
|
+
|
|
213
|
+
worksheet = workbook.worksheets[options.worksheet_index]
|
|
214
|
+
|
|
215
|
+
# Get the used range of the worksheet
|
|
216
|
+
rows_data = CSVHandler._get_worksheet_data(worksheet)
|
|
217
|
+
|
|
218
|
+
# Write to string buffer
|
|
219
|
+
output = io.StringIO()
|
|
220
|
+
|
|
221
|
+
# Write BOM if requested
|
|
222
|
+
if options.write_bom and options.encoding.lower().replace('-', '') == 'utf8':
|
|
223
|
+
output.write('\ufeff')
|
|
224
|
+
|
|
225
|
+
writer = csv.writer(
|
|
226
|
+
output,
|
|
227
|
+
delimiter=options.delimiter,
|
|
228
|
+
quotechar=options.quote_char,
|
|
229
|
+
quoting=options.quoting,
|
|
230
|
+
lineterminator=options.line_terminator
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
# Write data rows
|
|
234
|
+
for row_data in rows_data:
|
|
235
|
+
formatted_row = [
|
|
236
|
+
CSVHandler._format_cell_for_csv(cell, options)
|
|
237
|
+
for cell in row_data
|
|
238
|
+
]
|
|
239
|
+
writer.writerow(formatted_row)
|
|
240
|
+
|
|
241
|
+
return output.getvalue()
|
|
242
|
+
|
|
243
|
+
@staticmethod
|
|
244
|
+
def load_csv(workbook, file_path: str, options: Optional[CSVLoadOptions] = None) -> None:
|
|
245
|
+
"""
|
|
246
|
+
Loads a CSV file into a workbook.
|
|
247
|
+
|
|
248
|
+
The CSV data is loaded into the first worksheet of the workbook,
|
|
249
|
+
replacing any existing data.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
workbook: The Workbook object to load data into.
|
|
253
|
+
file_path (str): Path to the CSV file to load.
|
|
254
|
+
options (CSVLoadOptions, optional): Import options. Uses defaults if None.
|
|
255
|
+
|
|
256
|
+
Raises:
|
|
257
|
+
FileNotFoundError: If the CSV file does not exist.
|
|
258
|
+
IOError: If the file cannot be read.
|
|
259
|
+
|
|
260
|
+
Examples:
|
|
261
|
+
>>> wb = Workbook()
|
|
262
|
+
>>> CSVHandler.load_csv(wb, 'data.csv')
|
|
263
|
+
|
|
264
|
+
>>> options = CSVLoadOptions()
|
|
265
|
+
>>> options.delimiter = ';'
|
|
266
|
+
>>> options.encoding = 'latin-1'
|
|
267
|
+
>>> CSVHandler.load_csv(wb, 'data.csv', options)
|
|
268
|
+
"""
|
|
269
|
+
if options is None:
|
|
270
|
+
options = CSVLoadOptions()
|
|
271
|
+
|
|
272
|
+
with open(file_path, 'r', encoding=options.encoding, newline='') as f:
|
|
273
|
+
CSVHandler._load_csv_from_reader(workbook, f, options)
|
|
274
|
+
|
|
275
|
+
@staticmethod
|
|
276
|
+
def load_csv_from_string(workbook, csv_content: str, options: Optional[CSVLoadOptions] = None) -> None:
|
|
277
|
+
"""
|
|
278
|
+
Loads CSV data from a string into a workbook.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
workbook: The Workbook object to load data into.
|
|
282
|
+
csv_content (str): The CSV content as a string.
|
|
283
|
+
options (CSVLoadOptions, optional): Import options. Uses defaults if None.
|
|
284
|
+
|
|
285
|
+
Examples:
|
|
286
|
+
>>> wb = Workbook()
|
|
287
|
+
>>> csv_data = "Name,Age\\nAlice,30\\nBob,25"
|
|
288
|
+
>>> CSVHandler.load_csv_from_string(wb, csv_data)
|
|
289
|
+
"""
|
|
290
|
+
if options is None:
|
|
291
|
+
options = CSVLoadOptions()
|
|
292
|
+
|
|
293
|
+
# Remove BOM if present
|
|
294
|
+
if csv_content.startswith('\ufeff'):
|
|
295
|
+
csv_content = csv_content[1:]
|
|
296
|
+
|
|
297
|
+
f = io.StringIO(csv_content)
|
|
298
|
+
CSVHandler._load_csv_from_reader(workbook, f, options)
|
|
299
|
+
|
|
300
|
+
@staticmethod
|
|
301
|
+
def _load_csv_from_reader(workbook, reader, options: CSVLoadOptions) -> None:
|
|
302
|
+
"""
|
|
303
|
+
Internal method to load CSV data from a file-like reader.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
workbook: The Workbook object to load data into.
|
|
307
|
+
reader: File-like object to read from.
|
|
308
|
+
options (CSVLoadOptions): Import options.
|
|
309
|
+
"""
|
|
310
|
+
# Get the first worksheet (or create one if needed)
|
|
311
|
+
if len(workbook.worksheets) == 0:
|
|
312
|
+
from .worksheet import Worksheet
|
|
313
|
+
workbook._worksheets.append(Worksheet("Sheet1"))
|
|
314
|
+
|
|
315
|
+
worksheet = workbook.worksheets[0]
|
|
316
|
+
|
|
317
|
+
# Clear existing cell data
|
|
318
|
+
worksheet.cells._cells.clear()
|
|
319
|
+
|
|
320
|
+
# Create CSV reader
|
|
321
|
+
csv_reader = csv.reader(
|
|
322
|
+
reader,
|
|
323
|
+
delimiter=options.delimiter,
|
|
324
|
+
quotechar=options.quote_char,
|
|
325
|
+
escapechar=options.escape_char
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
# Skip rows if specified
|
|
329
|
+
for _ in range(options.skip_rows):
|
|
330
|
+
try:
|
|
331
|
+
next(csv_reader)
|
|
332
|
+
except StopIteration:
|
|
333
|
+
return
|
|
334
|
+
|
|
335
|
+
# Read header if specified
|
|
336
|
+
headers = None
|
|
337
|
+
if options.has_header:
|
|
338
|
+
try:
|
|
339
|
+
headers = next(csv_reader)
|
|
340
|
+
# Write headers to first row
|
|
341
|
+
for col_idx, header in enumerate(headers, start=1):
|
|
342
|
+
worksheet.cells.cell(row=1, column=col_idx).value = header
|
|
343
|
+
except StopIteration:
|
|
344
|
+
return
|
|
345
|
+
|
|
346
|
+
# Starting row (1 if no header, 2 if header)
|
|
347
|
+
start_row = 2 if options.has_header else 1
|
|
348
|
+
|
|
349
|
+
# Read data rows
|
|
350
|
+
for row_idx, row in enumerate(csv_reader, start=start_row):
|
|
351
|
+
for col_idx, value in enumerate(row, start=1):
|
|
352
|
+
# Auto-detect types if enabled
|
|
353
|
+
if options.auto_detect_types:
|
|
354
|
+
value = CSVHandler._parse_value(value, options)
|
|
355
|
+
|
|
356
|
+
worksheet.cells.cell(row=row_idx, column=col_idx).value = value
|
|
357
|
+
|
|
358
|
+
@staticmethod
|
|
359
|
+
def _get_worksheet_data(worksheet) -> List[List[Any]]:
|
|
360
|
+
"""
|
|
361
|
+
Extracts all cell data from a worksheet as a 2D list.
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
worksheet: The Worksheet object to extract data from.
|
|
365
|
+
|
|
366
|
+
Returns:
|
|
367
|
+
list: 2D list of Cell objects (or None), organized by rows.
|
|
368
|
+
"""
|
|
369
|
+
from .cells import Cells
|
|
370
|
+
|
|
371
|
+
cells_dict = worksheet.cells._cells
|
|
372
|
+
|
|
373
|
+
min_row = 1
|
|
374
|
+
min_col = 1
|
|
375
|
+
max_row = 0
|
|
376
|
+
max_col = 0
|
|
377
|
+
|
|
378
|
+
if hasattr(worksheet, '_dimension'):
|
|
379
|
+
min_row, min_col, max_row, max_col = worksheet._dimension
|
|
380
|
+
else:
|
|
381
|
+
if not cells_dict:
|
|
382
|
+
return []
|
|
383
|
+
|
|
384
|
+
# Find the dimensions by parsing cell references
|
|
385
|
+
for ref in cells_dict.keys():
|
|
386
|
+
row, col = Cells.coordinate_from_string(ref)
|
|
387
|
+
if row > max_row:
|
|
388
|
+
max_row = row
|
|
389
|
+
if col > max_col:
|
|
390
|
+
max_col = col
|
|
391
|
+
|
|
392
|
+
if max_row == 0 or max_col == 0:
|
|
393
|
+
return []
|
|
394
|
+
|
|
395
|
+
# Build 2D array
|
|
396
|
+
rows_data = []
|
|
397
|
+
for row_idx in range(min_row, max_row + 1):
|
|
398
|
+
row_data = []
|
|
399
|
+
for col_idx in range(min_col, max_col + 1):
|
|
400
|
+
ref = Cells.coordinate_to_string(row_idx, col_idx)
|
|
401
|
+
cell = cells_dict.get(ref)
|
|
402
|
+
row_data.append(cell)
|
|
403
|
+
rows_data.append(row_data)
|
|
404
|
+
|
|
405
|
+
return rows_data
|
|
406
|
+
|
|
407
|
+
@staticmethod
|
|
408
|
+
def _format_cell_for_csv(cell, options: CSVSaveOptions) -> str:
|
|
409
|
+
"""
|
|
410
|
+
Formats a cell for CSV output, applying number formats when appropriate.
|
|
411
|
+
|
|
412
|
+
Args:
|
|
413
|
+
cell: The Cell object (or None).
|
|
414
|
+
options (CSVSaveOptions): Export options.
|
|
415
|
+
|
|
416
|
+
Returns:
|
|
417
|
+
str: The formatted string value.
|
|
418
|
+
"""
|
|
419
|
+
if cell is None:
|
|
420
|
+
return ''
|
|
421
|
+
|
|
422
|
+
number_format = None
|
|
423
|
+
if hasattr(cell, 'style') and hasattr(cell.style, 'number_format'):
|
|
424
|
+
number_format = cell.style.number_format
|
|
425
|
+
|
|
426
|
+
return CSVHandler._format_value_for_csv(cell.value, options, number_format)
|
|
427
|
+
|
|
428
|
+
@staticmethod
|
|
429
|
+
def _format_value_for_csv(value: Any, options: CSVSaveOptions, number_format: Optional[str] = None) -> str:
|
|
430
|
+
"""
|
|
431
|
+
Formats a cell value for CSV output.
|
|
432
|
+
|
|
433
|
+
Args:
|
|
434
|
+
value: The cell value to format.
|
|
435
|
+
options (CSVSaveOptions): Export options.
|
|
436
|
+
number_format (str, optional): Cell number format string.
|
|
437
|
+
|
|
438
|
+
Returns:
|
|
439
|
+
str: The formatted string value.
|
|
440
|
+
"""
|
|
441
|
+
if value is None:
|
|
442
|
+
return ''
|
|
443
|
+
|
|
444
|
+
# Handle datetime types
|
|
445
|
+
if isinstance(value, datetime):
|
|
446
|
+
return value.strftime(options.datetime_format)
|
|
447
|
+
|
|
448
|
+
if isinstance(value, date):
|
|
449
|
+
return value.strftime(options.date_format)
|
|
450
|
+
|
|
451
|
+
if isinstance(value, time):
|
|
452
|
+
return value.strftime(options.time_format)
|
|
453
|
+
|
|
454
|
+
# Handle boolean
|
|
455
|
+
if isinstance(value, bool):
|
|
456
|
+
return 'TRUE' if value else 'FALSE'
|
|
457
|
+
|
|
458
|
+
# Handle numeric types
|
|
459
|
+
if isinstance(value, (int, float)):
|
|
460
|
+
formatted_number = CSVHandler._format_number_with_format(value, number_format)
|
|
461
|
+
if formatted_number is not None:
|
|
462
|
+
return formatted_number
|
|
463
|
+
# Check if it's an integer stored as float
|
|
464
|
+
if isinstance(value, float) and value.is_integer():
|
|
465
|
+
return str(int(value))
|
|
466
|
+
return str(value)
|
|
467
|
+
|
|
468
|
+
# Handle strings
|
|
469
|
+
return str(value)
|
|
470
|
+
|
|
471
|
+
@staticmethod
|
|
472
|
+
def _format_number_with_format(value: float, format_code: Optional[str]) -> Optional[str]:
|
|
473
|
+
"""
|
|
474
|
+
Formats a numeric value using an Excel-style number format string.
|
|
475
|
+
|
|
476
|
+
Args:
|
|
477
|
+
value (float): The numeric value to format.
|
|
478
|
+
format_code (str, optional): The Excel number format code.
|
|
479
|
+
|
|
480
|
+
Returns:
|
|
481
|
+
str or None: Formatted value if a usable format is provided, otherwise None.
|
|
482
|
+
"""
|
|
483
|
+
if format_code is None:
|
|
484
|
+
return None
|
|
485
|
+
|
|
486
|
+
if format_code == '' or format_code.lower() == 'general' or format_code == '@':
|
|
487
|
+
return None
|
|
488
|
+
|
|
489
|
+
sections = format_code.split(';')
|
|
490
|
+
section = sections[0] if sections else format_code
|
|
491
|
+
value_to_format = value
|
|
492
|
+
|
|
493
|
+
if len(sections) > 1:
|
|
494
|
+
if value < 0:
|
|
495
|
+
section = sections[1]
|
|
496
|
+
value_to_format = abs(value)
|
|
497
|
+
elif value == 0 and len(sections) > 2:
|
|
498
|
+
section = sections[2]
|
|
499
|
+
else:
|
|
500
|
+
section = sections[0]
|
|
501
|
+
|
|
502
|
+
# Strip bracketed tokens like [Red], [>100], [$-409]
|
|
503
|
+
section = re.sub(r'\[[^\]]+\]', '', section)
|
|
504
|
+
|
|
505
|
+
# If no placeholders, return literal section
|
|
506
|
+
if not re.search(r'[0#?]', section):
|
|
507
|
+
return CSVHandler._clean_format_literal(section)
|
|
508
|
+
|
|
509
|
+
first_idx = None
|
|
510
|
+
last_idx = None
|
|
511
|
+
for idx, ch in enumerate(section):
|
|
512
|
+
if ch in '0#?':
|
|
513
|
+
if first_idx is None:
|
|
514
|
+
first_idx = idx
|
|
515
|
+
last_idx = idx
|
|
516
|
+
|
|
517
|
+
if first_idx is None:
|
|
518
|
+
return CSVHandler._clean_format_literal(section)
|
|
519
|
+
|
|
520
|
+
prefix_raw = section[:first_idx]
|
|
521
|
+
suffix_raw = section[last_idx + 1:]
|
|
522
|
+
prefix = CSVHandler._clean_format_literal(prefix_raw)
|
|
523
|
+
suffix = CSVHandler._clean_format_literal(suffix_raw)
|
|
524
|
+
|
|
525
|
+
has_percent = '%' in section
|
|
526
|
+
if has_percent:
|
|
527
|
+
value_to_format *= 100
|
|
528
|
+
|
|
529
|
+
# Scientific notation
|
|
530
|
+
if 'E' in section or 'e' in section:
|
|
531
|
+
decimals = 0
|
|
532
|
+
match = re.search(r'\.(?P<frac>[0#?]+)[eE]', section)
|
|
533
|
+
if match:
|
|
534
|
+
decimals = len(match.group('frac'))
|
|
535
|
+
formatted = f"{value_to_format:.{decimals}E}"
|
|
536
|
+
return f"{prefix}{formatted}{suffix}"
|
|
537
|
+
|
|
538
|
+
# Standard number format
|
|
539
|
+
number_pattern = section[first_idx:last_idx + 1]
|
|
540
|
+
pattern_clean = re.sub(r'[^0#.,]', '', number_pattern)
|
|
541
|
+
if '.' in pattern_clean:
|
|
542
|
+
int_part, frac_part = pattern_clean.split('.', 1)
|
|
543
|
+
else:
|
|
544
|
+
int_part, frac_part = pattern_clean, ''
|
|
545
|
+
|
|
546
|
+
use_grouping = ',' in int_part
|
|
547
|
+
min_decimals = frac_part.count('0')
|
|
548
|
+
max_decimals = sum(1 for ch in frac_part if ch in '0#')
|
|
549
|
+
|
|
550
|
+
if max_decimals == 0:
|
|
551
|
+
fmt = f",.0f" if use_grouping else ".0f"
|
|
552
|
+
formatted = format(value_to_format, fmt)
|
|
553
|
+
else:
|
|
554
|
+
fmt = f",.{max_decimals}f" if use_grouping else f".{max_decimals}f"
|
|
555
|
+
formatted = format(value_to_format, fmt)
|
|
556
|
+
if max_decimals > min_decimals and '.' in formatted:
|
|
557
|
+
int_text, frac_text = formatted.split('.', 1)
|
|
558
|
+
frac_text = frac_text.rstrip('0')
|
|
559
|
+
if len(frac_text) < min_decimals:
|
|
560
|
+
frac_text = frac_text.ljust(min_decimals, '0')
|
|
561
|
+
formatted = int_text if frac_text == '' else f"{int_text}.{frac_text}"
|
|
562
|
+
|
|
563
|
+
return f"{prefix}{formatted}{suffix}"
|
|
564
|
+
|
|
565
|
+
@staticmethod
|
|
566
|
+
def _clean_format_literal(text: str) -> str:
|
|
567
|
+
"""
|
|
568
|
+
Cleans format literals by removing Excel formatting directives.
|
|
569
|
+
|
|
570
|
+
Args:
|
|
571
|
+
text (str): Raw format text.
|
|
572
|
+
|
|
573
|
+
Returns:
|
|
574
|
+
str: Cleaned literal text.
|
|
575
|
+
"""
|
|
576
|
+
result = []
|
|
577
|
+
idx = 0
|
|
578
|
+
while idx < len(text):
|
|
579
|
+
ch = text[idx]
|
|
580
|
+
if ch == '"':
|
|
581
|
+
idx += 1
|
|
582
|
+
while idx < len(text) and text[idx] != '"':
|
|
583
|
+
result.append(text[idx])
|
|
584
|
+
idx += 1
|
|
585
|
+
idx += 1
|
|
586
|
+
continue
|
|
587
|
+
if ch in ('_', '*'):
|
|
588
|
+
idx += 2
|
|
589
|
+
continue
|
|
590
|
+
if ch == '\\':
|
|
591
|
+
if idx + 1 < len(text):
|
|
592
|
+
result.append(text[idx + 1])
|
|
593
|
+
idx += 2
|
|
594
|
+
else:
|
|
595
|
+
idx += 1
|
|
596
|
+
continue
|
|
597
|
+
result.append(ch)
|
|
598
|
+
idx += 1
|
|
599
|
+
return ''.join(result)
|
|
600
|
+
|
|
601
|
+
@staticmethod
|
|
602
|
+
def _parse_value(value_str: str, options: CSVLoadOptions) -> Any:
|
|
603
|
+
"""
|
|
604
|
+
Parses a string value and attempts to convert it to the appropriate type.
|
|
605
|
+
|
|
606
|
+
Args:
|
|
607
|
+
value_str (str): The string value to parse.
|
|
608
|
+
options (CSVLoadOptions): Import options containing type detection settings.
|
|
609
|
+
|
|
610
|
+
Returns:
|
|
611
|
+
The parsed value (int, float, bool, datetime, date, or str).
|
|
612
|
+
"""
|
|
613
|
+
# Handle empty strings
|
|
614
|
+
if not value_str or value_str.strip() == '':
|
|
615
|
+
return None
|
|
616
|
+
|
|
617
|
+
value_str = value_str.strip()
|
|
618
|
+
|
|
619
|
+
# Try boolean
|
|
620
|
+
if value_str in options.true_values:
|
|
621
|
+
return True
|
|
622
|
+
if value_str in options.false_values:
|
|
623
|
+
return False
|
|
624
|
+
|
|
625
|
+
# Try integer
|
|
626
|
+
try:
|
|
627
|
+
# Check for integer format (no decimal point, no exponential)
|
|
628
|
+
if '.' not in value_str and 'e' not in value_str.lower():
|
|
629
|
+
return int(value_str)
|
|
630
|
+
except ValueError:
|
|
631
|
+
pass
|
|
632
|
+
|
|
633
|
+
# Try float
|
|
634
|
+
try:
|
|
635
|
+
return float(value_str)
|
|
636
|
+
except ValueError:
|
|
637
|
+
pass
|
|
638
|
+
|
|
639
|
+
# Try date/datetime formats
|
|
640
|
+
for fmt in options.date_formats:
|
|
641
|
+
try:
|
|
642
|
+
dt = datetime.strptime(value_str, fmt)
|
|
643
|
+
# Return date if no time component, datetime otherwise
|
|
644
|
+
if '%H' in fmt or '%I' in fmt:
|
|
645
|
+
return dt
|
|
646
|
+
else:
|
|
647
|
+
return dt.date()
|
|
648
|
+
except ValueError:
|
|
649
|
+
continue
|
|
650
|
+
|
|
651
|
+
# Return as string
|
|
652
|
+
return value_str
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
def load_csv_workbook(file_path: str, options: Optional[CSVLoadOptions] = None):
|
|
656
|
+
"""
|
|
657
|
+
Convenience function to create a new Workbook from a CSV file.
|
|
658
|
+
|
|
659
|
+
Args:
|
|
660
|
+
file_path (str): Path to the CSV file to load.
|
|
661
|
+
options (CSVLoadOptions, optional): Import options. Uses defaults if None.
|
|
662
|
+
|
|
663
|
+
Returns:
|
|
664
|
+
Workbook: A new Workbook containing the CSV data.
|
|
665
|
+
|
|
666
|
+
Examples:
|
|
667
|
+
>>> wb = load_csv_workbook('data.csv')
|
|
668
|
+
>>> print(wb.worksheets[0].cells['A1'].value)
|
|
669
|
+
"""
|
|
670
|
+
from .workbook import Workbook
|
|
671
|
+
|
|
672
|
+
wb = Workbook()
|
|
673
|
+
CSVHandler.load_csv(wb, file_path, options)
|
|
674
|
+
return wb
|
|
675
|
+
|
|
676
|
+
|
|
677
|
+
def save_workbook_as_csv(workbook, file_path: str, options: Optional[CSVSaveOptions] = None) -> None:
|
|
678
|
+
"""
|
|
679
|
+
Convenience function to save a Workbook to a CSV file.
|
|
680
|
+
|
|
681
|
+
Args:
|
|
682
|
+
workbook: The Workbook object to export.
|
|
683
|
+
file_path (str): Path where the CSV file should be saved.
|
|
684
|
+
options (CSVSaveOptions, optional): Export options. Uses defaults if None.
|
|
685
|
+
|
|
686
|
+
Examples:
|
|
687
|
+
>>> wb = Workbook('data.xlsx')
|
|
688
|
+
>>> save_workbook_as_csv(wb, 'output.csv')
|
|
689
|
+
"""
|
|
690
|
+
CSVHandler.save_csv(workbook, file_path, options)
|