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.
Files changed (93) hide show
  1. aspose_cells/__init__.py +88 -0
  2. aspose_cells/auto_filter.py +527 -0
  3. aspose_cells/cell.py +483 -0
  4. aspose_cells/cell_value_handler.py +319 -0
  5. aspose_cells/cells.py +779 -0
  6. aspose_cells/cfb_handler.py +445 -0
  7. aspose_cells/cfb_writer.py +659 -0
  8. aspose_cells/cfb_writer_minimal.py +337 -0
  9. aspose_cells/comment_xml.py +475 -0
  10. aspose_cells/conditional_format.py +1185 -0
  11. aspose_cells/csv_handler.py +690 -0
  12. aspose_cells/data_validation.py +911 -0
  13. aspose_cells/document_properties.py +356 -0
  14. aspose_cells/encryption_crypto.py +247 -0
  15. aspose_cells/encryption_params.py +138 -0
  16. aspose_cells/hyperlink.py +372 -0
  17. aspose_cells/json_handler.py +185 -0
  18. aspose_cells/markdown_handler.py +583 -0
  19. aspose_cells/shared_strings.py +101 -0
  20. aspose_cells/style.py +841 -0
  21. aspose_cells/workbook.py +499 -0
  22. aspose_cells/workbook_hash_password.py +68 -0
  23. aspose_cells/workbook_properties.py +712 -0
  24. aspose_cells/worksheet.py +570 -0
  25. aspose_cells/worksheet_properties.py +1239 -0
  26. aspose_cells/xlsx_encryptor.py +403 -0
  27. aspose_cells/xml_autofilter_loader.py +195 -0
  28. aspose_cells/xml_autofilter_saver.py +173 -0
  29. aspose_cells/xml_conditional_format_loader.py +215 -0
  30. aspose_cells/xml_conditional_format_saver.py +351 -0
  31. aspose_cells/xml_datavalidation_loader.py +239 -0
  32. aspose_cells/xml_datavalidation_saver.py +245 -0
  33. aspose_cells/xml_hyperlink_handler.py +323 -0
  34. aspose_cells/xml_loader.py +986 -0
  35. aspose_cells/xml_properties_loader.py +512 -0
  36. aspose_cells/xml_properties_saver.py +607 -0
  37. aspose_cells/xml_saver.py +1306 -0
  38. aspose_cells_foss-26.2.2.dist-info/METADATA +190 -0
  39. aspose_cells_foss-26.2.2.dist-info/RECORD +41 -0
  40. {aspose_cells_foss-25.12.1.dist-info → aspose_cells_foss-26.2.2.dist-info}/WHEEL +1 -1
  41. aspose_cells_foss-26.2.2.dist-info/top_level.txt +1 -0
  42. aspose/__init__.py +0 -14
  43. aspose/cells/__init__.py +0 -31
  44. aspose/cells/cell.py +0 -350
  45. aspose/cells/constants.py +0 -44
  46. aspose/cells/converters/__init__.py +0 -13
  47. aspose/cells/converters/csv_converter.py +0 -55
  48. aspose/cells/converters/json_converter.py +0 -46
  49. aspose/cells/converters/markdown_converter.py +0 -453
  50. aspose/cells/drawing/__init__.py +0 -17
  51. aspose/cells/drawing/anchor.py +0 -172
  52. aspose/cells/drawing/collection.py +0 -233
  53. aspose/cells/drawing/image.py +0 -338
  54. aspose/cells/formats.py +0 -80
  55. aspose/cells/formula/__init__.py +0 -10
  56. aspose/cells/formula/evaluator.py +0 -360
  57. aspose/cells/formula/functions.py +0 -433
  58. aspose/cells/formula/tokenizer.py +0 -340
  59. aspose/cells/io/__init__.py +0 -27
  60. aspose/cells/io/csv/__init__.py +0 -8
  61. aspose/cells/io/csv/reader.py +0 -88
  62. aspose/cells/io/csv/writer.py +0 -98
  63. aspose/cells/io/factory.py +0 -138
  64. aspose/cells/io/interfaces.py +0 -48
  65. aspose/cells/io/json/__init__.py +0 -8
  66. aspose/cells/io/json/reader.py +0 -126
  67. aspose/cells/io/json/writer.py +0 -119
  68. aspose/cells/io/md/__init__.py +0 -8
  69. aspose/cells/io/md/reader.py +0 -161
  70. aspose/cells/io/md/writer.py +0 -334
  71. aspose/cells/io/models.py +0 -64
  72. aspose/cells/io/xlsx/__init__.py +0 -9
  73. aspose/cells/io/xlsx/constants.py +0 -312
  74. aspose/cells/io/xlsx/image_writer.py +0 -311
  75. aspose/cells/io/xlsx/reader.py +0 -284
  76. aspose/cells/io/xlsx/writer.py +0 -931
  77. aspose/cells/plugins/__init__.py +0 -6
  78. aspose/cells/plugins/docling_backend/__init__.py +0 -7
  79. aspose/cells/plugins/docling_backend/backend.py +0 -535
  80. aspose/cells/plugins/markitdown_plugin/__init__.py +0 -15
  81. aspose/cells/plugins/markitdown_plugin/plugin.py +0 -128
  82. aspose/cells/range.py +0 -210
  83. aspose/cells/style.py +0 -287
  84. aspose/cells/utils/__init__.py +0 -54
  85. aspose/cells/utils/coordinates.py +0 -68
  86. aspose/cells/utils/exceptions.py +0 -43
  87. aspose/cells/utils/validation.py +0 -102
  88. aspose/cells/workbook.py +0 -352
  89. aspose/cells/worksheet.py +0 -670
  90. aspose_cells_foss-25.12.1.dist-info/METADATA +0 -189
  91. aspose_cells_foss-25.12.1.dist-info/RECORD +0 -53
  92. aspose_cells_foss-25.12.1.dist-info/entry_points.txt +0 -2
  93. 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)