aspose-cells-foss 25.12.1__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 (53) hide show
  1. aspose/__init__.py +14 -0
  2. aspose/cells/__init__.py +31 -0
  3. aspose/cells/cell.py +350 -0
  4. aspose/cells/constants.py +44 -0
  5. aspose/cells/converters/__init__.py +13 -0
  6. aspose/cells/converters/csv_converter.py +55 -0
  7. aspose/cells/converters/json_converter.py +46 -0
  8. aspose/cells/converters/markdown_converter.py +453 -0
  9. aspose/cells/drawing/__init__.py +17 -0
  10. aspose/cells/drawing/anchor.py +172 -0
  11. aspose/cells/drawing/collection.py +233 -0
  12. aspose/cells/drawing/image.py +338 -0
  13. aspose/cells/formats.py +80 -0
  14. aspose/cells/formula/__init__.py +10 -0
  15. aspose/cells/formula/evaluator.py +360 -0
  16. aspose/cells/formula/functions.py +433 -0
  17. aspose/cells/formula/tokenizer.py +340 -0
  18. aspose/cells/io/__init__.py +27 -0
  19. aspose/cells/io/csv/__init__.py +8 -0
  20. aspose/cells/io/csv/reader.py +88 -0
  21. aspose/cells/io/csv/writer.py +98 -0
  22. aspose/cells/io/factory.py +138 -0
  23. aspose/cells/io/interfaces.py +48 -0
  24. aspose/cells/io/json/__init__.py +8 -0
  25. aspose/cells/io/json/reader.py +126 -0
  26. aspose/cells/io/json/writer.py +119 -0
  27. aspose/cells/io/md/__init__.py +8 -0
  28. aspose/cells/io/md/reader.py +161 -0
  29. aspose/cells/io/md/writer.py +334 -0
  30. aspose/cells/io/models.py +64 -0
  31. aspose/cells/io/xlsx/__init__.py +9 -0
  32. aspose/cells/io/xlsx/constants.py +312 -0
  33. aspose/cells/io/xlsx/image_writer.py +311 -0
  34. aspose/cells/io/xlsx/reader.py +284 -0
  35. aspose/cells/io/xlsx/writer.py +931 -0
  36. aspose/cells/plugins/__init__.py +6 -0
  37. aspose/cells/plugins/docling_backend/__init__.py +7 -0
  38. aspose/cells/plugins/docling_backend/backend.py +535 -0
  39. aspose/cells/plugins/markitdown_plugin/__init__.py +15 -0
  40. aspose/cells/plugins/markitdown_plugin/plugin.py +128 -0
  41. aspose/cells/range.py +210 -0
  42. aspose/cells/style.py +287 -0
  43. aspose/cells/utils/__init__.py +54 -0
  44. aspose/cells/utils/coordinates.py +68 -0
  45. aspose/cells/utils/exceptions.py +43 -0
  46. aspose/cells/utils/validation.py +102 -0
  47. aspose/cells/workbook.py +352 -0
  48. aspose/cells/worksheet.py +670 -0
  49. aspose_cells_foss-25.12.1.dist-info/METADATA +189 -0
  50. aspose_cells_foss-25.12.1.dist-info/RECORD +53 -0
  51. aspose_cells_foss-25.12.1.dist-info/WHEEL +5 -0
  52. aspose_cells_foss-25.12.1.dist-info/entry_points.txt +2 -0
  53. aspose_cells_foss-25.12.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,102 @@
1
+ """
2
+ Data validation utilities for Excel operations.
3
+ """
4
+
5
+ from typing import Union
6
+ from datetime import datetime
7
+ import re
8
+
9
+ from ..formats import CellValue
10
+ from ..constants import (
11
+ MAX_SHEET_NAME_LENGTH, INVALID_SHEET_NAME_CHARS,
12
+ CELL_REF_PATTERN, DEFAULT_SHEET_NAME
13
+ )
14
+
15
+
16
+ def is_numeric_string(value: str) -> bool:
17
+ """Check if string represents a numeric value."""
18
+ try:
19
+ float(value)
20
+ return True
21
+ except (ValueError, TypeError):
22
+ return False
23
+
24
+
25
+ def is_formula(value: str) -> bool:
26
+ """Check if string represents an Excel formula."""
27
+ return isinstance(value, str) and value.startswith('=')
28
+
29
+
30
+ def is_date_value(value: CellValue) -> bool:
31
+ """Check if value represents a date."""
32
+ return isinstance(value, datetime)
33
+
34
+
35
+ def infer_data_type(value: CellValue) -> str:
36
+ """Infer Excel data type from Python value."""
37
+ if value is None:
38
+ return 'empty'
39
+ elif isinstance(value, bool):
40
+ return 'boolean'
41
+ elif isinstance(value, (int, float)):
42
+ return 'number'
43
+ elif isinstance(value, datetime):
44
+ return 'date'
45
+ elif isinstance(value, str):
46
+ if is_formula(value):
47
+ return 'formula'
48
+ elif is_numeric_string(value):
49
+ return 'number'
50
+ else:
51
+ return 'string'
52
+ else:
53
+ return 'string'
54
+
55
+
56
+ def validate_sheet_name(name: str) -> bool:
57
+ """Validate Excel worksheet name."""
58
+ if not name or len(name) > MAX_SHEET_NAME_LENGTH:
59
+ return False
60
+
61
+ return not any(char in name for char in INVALID_SHEET_NAME_CHARS)
62
+
63
+
64
+ def sanitize_sheet_name(name: str) -> str:
65
+ """Sanitize worksheet name for Excel compatibility."""
66
+ if not name:
67
+ return DEFAULT_SHEET_NAME + "1"
68
+
69
+ # Remove invalid characters
70
+ for char in INVALID_SHEET_NAME_CHARS:
71
+ name = name.replace(char, '_')
72
+
73
+ # Truncate to maximum length
74
+ if len(name) > MAX_SHEET_NAME_LENGTH:
75
+ name = name[:MAX_SHEET_NAME_LENGTH]
76
+
77
+ return name or DEFAULT_SHEET_NAME + "1"
78
+
79
+
80
+ def validate_cell_reference(ref: str) -> bool:
81
+ """Validate Excel cell reference format (e.g., A1, Z99, AA100)."""
82
+ if not ref or not isinstance(ref, str):
83
+ return False
84
+
85
+ return bool(re.match(CELL_REF_PATTERN, ref.upper()))
86
+
87
+
88
+ def convert_value(value: CellValue, target_type: str, default: CellValue = None) -> CellValue:
89
+ """Convert value to target type with fallback to default."""
90
+ try:
91
+ if target_type == 'string':
92
+ return str(value) if value is not None else ""
93
+ elif target_type == 'int':
94
+ return int(float(str(value)))
95
+ elif target_type == 'float':
96
+ return float(value)
97
+ elif target_type == 'bool':
98
+ return bool(value)
99
+ else:
100
+ return value
101
+ except (ValueError, TypeError):
102
+ return default
@@ -0,0 +1,352 @@
1
+ """
2
+ Workbook implementation with unified API and multiple file format support.
3
+ """
4
+
5
+ from typing import Dict, List, Optional, Union
6
+ from pathlib import Path
7
+
8
+ from .worksheet import Worksheet
9
+ from .formats import FileFormat, ConversionOptions
10
+ from .utils import (
11
+ sanitize_sheet_name,
12
+ WorksheetNotFoundError,
13
+ FileFormatError,
14
+ ExportError
15
+ )
16
+ from .io.factory import FormatHandlerFactory
17
+ from .io.models import WorkbookData
18
+
19
+
20
+
21
+
22
+ class WorksheetCollection:
23
+ """Collection manager for worksheets with multiple access patterns."""
24
+
25
+ def __init__(self, workbook: 'Workbook'):
26
+ self._workbook = workbook
27
+
28
+ def add(self, name: str) -> Worksheet:
29
+ """Add new worksheet with specified name."""
30
+ clean_name = sanitize_sheet_name(name)
31
+ if clean_name in self._workbook._worksheets:
32
+ # Generate unique name
33
+ counter = 1
34
+ base_name = clean_name
35
+ while clean_name in self._workbook._worksheets:
36
+ clean_name = f"{base_name}_{counter}"
37
+ counter += 1
38
+
39
+ worksheet = Worksheet(self._workbook, clean_name)
40
+ self._workbook._worksheets[clean_name] = worksheet
41
+ return worksheet
42
+
43
+ def remove(self, name: Union[str, int, Worksheet]):
44
+ """Remove worksheet by name, index, or object."""
45
+ if isinstance(name, Worksheet):
46
+ name = name.name
47
+ elif isinstance(name, int):
48
+ sheets = list(self._workbook._worksheets.values())
49
+ if 0 <= name < len(sheets):
50
+ name = sheets[name].name
51
+ else:
52
+ raise WorksheetNotFoundError(f"Worksheet index {name} out of range")
53
+
54
+ if name not in self._workbook._worksheets:
55
+ raise WorksheetNotFoundError(f"Worksheet '{name}' not found")
56
+
57
+ # Don't allow removing the last worksheet
58
+ if len(self._workbook._worksheets) <= 1:
59
+ raise WorksheetNotFoundError("Cannot remove the last worksheet")
60
+
61
+ # Update active sheet if necessary
62
+ if self._workbook._active_sheet and self._workbook._active_sheet.name == name:
63
+ remaining_sheets = [ws for ws in self._workbook._worksheets.values() if ws.name != name]
64
+ self._workbook._active_sheet = remaining_sheets[0]
65
+
66
+ del self._workbook._worksheets[name]
67
+
68
+ def __getitem__(self, key: Union[str, int]) -> Worksheet:
69
+ """Get worksheet by name or index."""
70
+ if isinstance(key, str):
71
+ if key not in self._workbook._worksheets:
72
+ raise WorksheetNotFoundError(f"Worksheet '{key}' not found")
73
+ return self._workbook._worksheets[key]
74
+ elif isinstance(key, int):
75
+ sheets = list(self._workbook._worksheets.values())
76
+ if 0 <= key < len(sheets):
77
+ return sheets[key]
78
+ else:
79
+ raise WorksheetNotFoundError(f"Worksheet index {key} out of range")
80
+ else:
81
+ raise WorksheetNotFoundError(f"Invalid worksheet key: {key}")
82
+
83
+ def __len__(self) -> int:
84
+ """Number of worksheets."""
85
+ return len(self._workbook._worksheets)
86
+
87
+ def __iter__(self):
88
+ """Iterate over worksheets."""
89
+ return iter(self._workbook._worksheets.values())
90
+
91
+ def __contains__(self, name: str) -> bool:
92
+ """Check if worksheet exists."""
93
+ return name in self._workbook._worksheets
94
+
95
+
96
+ class Workbook:
97
+ """Excel workbook with unified API and multiple access patterns."""
98
+
99
+ def __init__(self, filename: Optional[Union[str, Path]] = None):
100
+ self._filename: Optional[Path] = None
101
+ self._worksheets: Dict[str, Worksheet] = {}
102
+ self._active_sheet: Optional[Worksheet] = None
103
+ self._shared_strings: List[str] = []
104
+ self._properties: Dict[str, Union[str, int, float, bool]] = {}
105
+
106
+ # Initialize with default worksheet
107
+ default_sheet = Worksheet(self, "Sheet1")
108
+ self._worksheets["Sheet1"] = default_sheet
109
+ self._active_sheet = default_sheet
110
+
111
+ if filename:
112
+ self._load_from_file(filename)
113
+
114
+ @classmethod
115
+ def load(cls, filename: Union[str, Path]) -> 'Workbook':
116
+ """Load workbook from file."""
117
+ return cls(filename)
118
+
119
+ @property
120
+ def active(self) -> Worksheet:
121
+ """Get active worksheet."""
122
+ if self._active_sheet is None and self._worksheets:
123
+ self._active_sheet = next(iter(self._worksheets.values()))
124
+ return self._active_sheet
125
+
126
+ @active.setter
127
+ def active(self, value: Union[Worksheet, str, int]):
128
+ """Set active worksheet by object, name, or index."""
129
+ if isinstance(value, Worksheet):
130
+ if value in self._worksheets.values():
131
+ self._active_sheet = value
132
+ else:
133
+ raise WorksheetNotFoundError("Worksheet not in this workbook")
134
+ elif isinstance(value, str):
135
+ if value in self._worksheets:
136
+ self._active_sheet = self._worksheets[value]
137
+ else:
138
+ raise WorksheetNotFoundError(f"Worksheet '{value}' not found")
139
+ elif isinstance(value, int):
140
+ sheets = list(self._worksheets.values())
141
+ if 0 <= value < len(sheets):
142
+ self._active_sheet = sheets[value]
143
+ else:
144
+ raise WorksheetNotFoundError(f"Worksheet index {value} out of range")
145
+ else:
146
+ raise WorksheetNotFoundError(f"Invalid active sheet value: {value}")
147
+
148
+ @property
149
+ def worksheets(self) -> WorksheetCollection:
150
+ """Get worksheet collection manager."""
151
+ return WorksheetCollection(self)
152
+
153
+ @property
154
+ def sheetnames(self) -> List[str]:
155
+ """Get list of worksheet names."""
156
+ return list(self._worksheets.keys())
157
+
158
+ def create_sheet(self, name: str = None, index: int = None) -> Worksheet:
159
+ """Create new worksheet with optional name and position."""
160
+ if name is None:
161
+ # Generate default name
162
+ counter = len(self._worksheets) + 1
163
+ while f"Sheet{counter}" in self._worksheets:
164
+ counter += 1
165
+ name = f"Sheet{counter}"
166
+
167
+ worksheet = self.worksheets.add(name)
168
+
169
+ # Handle index positioning if specified
170
+ if index is not None and 0 <= index < len(self._worksheets):
171
+ # Re-order worksheets to insert at specific position
172
+ sheet_items = list(self._worksheets.items())
173
+ # Remove the newly added sheet from its current position (last)
174
+ new_sheet_item = sheet_items.pop()
175
+ # Insert it at the specified index
176
+ sheet_items.insert(index, new_sheet_item)
177
+ # Rebuild the ordered dictionary
178
+ self._worksheets.clear()
179
+ for sheet_name, sheet_obj in sheet_items:
180
+ self._worksheets[sheet_name] = sheet_obj
181
+
182
+ return worksheet
183
+
184
+ def _load_from_file(self, filename: Union[str, Path]):
185
+ """Load workbook from file using unified format factory."""
186
+ self._filename = Path(filename)
187
+
188
+ if not self._filename.exists():
189
+ raise FileFormatError(f"File not found: {filename}")
190
+
191
+ # Try unified format handler first
192
+ handler = FormatHandlerFactory.get_handler(str(filename))
193
+ if handler:
194
+ handler.load_workbook(self, str(filename))
195
+ else:
196
+ # Fall back to legacy reader for unsupported formats
197
+ if self._filename.suffix.lower() not in ['.xlsx', '.xlsm', '.xltx', '.xltm']:
198
+ raise FileFormatError(f"Unsupported file format: {self._filename.suffix}")
199
+
200
+ from .io.xlsx.reader import XlsxReader
201
+ reader = XlsxReader()
202
+ reader.load_workbook(self, str(filename))
203
+
204
+ def save(self, filename: Optional[Union[str, Path]] = None,
205
+ format: Optional[Union[str, FileFormat]] = None, **kwargs):
206
+ """Save workbook to file with specified format using unified factory."""
207
+ if filename is None:
208
+ if self._filename is None:
209
+ raise FileFormatError("No filename specified and no previous filename available")
210
+ filename = self._filename
211
+ else:
212
+ filename = Path(filename)
213
+
214
+ # If format is specified, modify filename extension
215
+ if format is not None:
216
+ if isinstance(format, str):
217
+ # Convert string to FileFormat enum if possible
218
+ try:
219
+ format_enum = FileFormat(format)
220
+ filename = filename.with_suffix(format_enum.extension)
221
+ except ValueError:
222
+ # For unsupported format strings, try to use them directly
223
+ if not format.startswith('.'):
224
+ format = '.' + format
225
+ filename = filename.with_suffix(format)
226
+ else:
227
+ filename = filename.with_suffix(format.extension)
228
+
229
+ # Try unified format handler first
230
+ handler = FormatHandlerFactory.get_handler(str(filename))
231
+ if handler:
232
+ handler.save_workbook(self, str(filename), **kwargs)
233
+ else:
234
+ # Fall back to legacy writer
235
+ if format is None:
236
+ format = FileFormat.from_extension(filename)
237
+ elif isinstance(format, str):
238
+ try:
239
+ format = FileFormat(format)
240
+ except ValueError:
241
+ raise FileFormatError(f"Unsupported format: {format}")
242
+
243
+ from .io.xlsx.writer import XlsxWriter
244
+ writer = XlsxWriter()
245
+ writer.save_workbook(self, str(filename), **kwargs)
246
+
247
+ self._filename = Path(filename)
248
+
249
+ def exportAs(self, format: Union[str, FileFormat], **kwargs) -> str:
250
+ """Export workbook as string in specified format."""
251
+ # Convert string to FileFormat enum if needed
252
+ if isinstance(format, str):
253
+ try:
254
+ format_enum = FileFormat(format)
255
+ except ValueError:
256
+ raise ExportError(f"Unsupported export format: {format}")
257
+ else:
258
+ format_enum = format
259
+
260
+ if format_enum == FileFormat.JSON:
261
+ from .converters.json_converter import JsonConverter
262
+ converter = JsonConverter()
263
+ return converter.convert_workbook(self, **kwargs)
264
+ elif format_enum == FileFormat.CSV:
265
+ from .converters.csv_converter import CsvConverter
266
+ converter = CsvConverter()
267
+ return converter.convert_workbook(self, **kwargs)
268
+ elif format_enum == FileFormat.MARKDOWN:
269
+ from .converters.markdown_converter import MarkdownConverter
270
+ converter = MarkdownConverter()
271
+ return converter.convert_workbook(self, **kwargs)
272
+ else:
273
+ raise ExportError(f"Unsupported export format: {format_enum.value}")
274
+
275
+ def copy_worksheet(self, from_worksheet: Union[Worksheet, str]) -> Worksheet:
276
+ """Create a copy of existing worksheet."""
277
+ if isinstance(from_worksheet, str):
278
+ if from_worksheet not in self._worksheets:
279
+ raise WorksheetNotFoundError(f"Source worksheet '{from_worksheet}' not found")
280
+ source = self._worksheets[from_worksheet]
281
+ else:
282
+ source = from_worksheet
283
+
284
+ # Generate new name
285
+ base_name = f"Copy of {source.name}"
286
+ new_name = base_name
287
+ counter = 1
288
+ while new_name in self._worksheets:
289
+ new_name = f"{base_name} ({counter})"
290
+ counter += 1
291
+
292
+ # Create new worksheet
293
+ new_worksheet = self.create_sheet(new_name)
294
+
295
+ # Copy all cell data and formatting
296
+ for coord, cell in source._cells.items():
297
+ row, col = coord
298
+ new_cell = new_worksheet.cell(row, col, cell.value)
299
+ if cell._style:
300
+ new_cell._style = cell._style.copy()
301
+ new_cell._number_format = cell._number_format
302
+ new_cell._hyperlink = cell._hyperlink
303
+ new_cell._comment = cell._comment
304
+
305
+ # Copy other properties
306
+ new_worksheet._merged_ranges = source._merged_ranges.copy()
307
+ new_worksheet._row_heights = source._row_heights.copy()
308
+ new_worksheet._column_widths = source._column_widths.copy()
309
+ new_worksheet._freeze_panes = source._freeze_panes
310
+
311
+ return new_worksheet
312
+
313
+ def convert_to(self, target_format: str, output_path: str, **options) -> None:
314
+ """Convert workbook to different format using unified data model."""
315
+ # Convert workbook to unified data model
316
+ data = WorkbookData.from_workbook(self)
317
+
318
+ # Get target format handler
319
+ handler = FormatHandlerFactory.get_handler(output_path)
320
+ if not handler:
321
+ raise FileFormatError(f"Unsupported target format: {Path(output_path).suffix}")
322
+
323
+ # Write using unified data model
324
+ handler.write_from_data(data, output_path, **options)
325
+
326
+ def close(self):
327
+ """Close workbook and release resources."""
328
+ self._worksheets.clear()
329
+ self._active_sheet = None
330
+ self._shared_strings.clear()
331
+ self._properties.clear()
332
+
333
+ @property
334
+ def properties(self) -> Dict[str, Union[str, int, float, bool]]:
335
+ """Get workbook properties."""
336
+ return self._properties
337
+
338
+ def __enter__(self):
339
+ """Context manager entry."""
340
+ return self
341
+
342
+ def __exit__(self, exc_type, exc_val, exc_tb):
343
+ """Context manager exit."""
344
+ self.close()
345
+
346
+ def __str__(self) -> str:
347
+ """String representation."""
348
+ return f"Workbook({len(self._worksheets)} sheets)"
349
+
350
+ def __repr__(self) -> str:
351
+ """Debug representation."""
352
+ return f"Workbook(sheets={list(self._worksheets.keys())}, active='{self.active.name if self.active else None}')"