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,233 @@
1
+ """
2
+ Image collection management for worksheets.
3
+ """
4
+
5
+ from typing import List, Union, Optional, Iterator
6
+ from pathlib import Path
7
+ import io
8
+
9
+ from .image import Image, ImageFormat
10
+ from .anchor import Anchor
11
+
12
+
13
+ class ImageCollection:
14
+ """Collection manager for worksheet images with multiple access patterns."""
15
+
16
+ def __init__(self, worksheet: 'Worksheet'):
17
+ self._worksheet = worksheet
18
+ self._images: List[Image] = []
19
+
20
+ def add(self, source: Union[str, Path, bytes, io.BytesIO, Image],
21
+ cell_ref: str = "A1",
22
+ width: Optional[int] = None,
23
+ height: Optional[int] = None,
24
+ name: Optional[str] = None) -> Image:
25
+ """
26
+ Add image to worksheet at specified cell.
27
+
28
+ Args:
29
+ source: Image source (file path, bytes, Image object)
30
+ cell_ref: Cell reference for positioning (default: "A1")
31
+ width: Optional width in pixels
32
+ height: Optional height in pixels
33
+ name: Optional image name
34
+
35
+ Returns:
36
+ Image: Added image object
37
+ """
38
+ if isinstance(source, Image):
39
+ image = source.copy()
40
+ else:
41
+ image = Image(source)
42
+
43
+ # Set positioning
44
+ image.position_at(cell_ref)
45
+
46
+ # Resize if specified
47
+ if width is not None or height is not None:
48
+ image.resize(width, height)
49
+
50
+ # Set name
51
+ if name is not None:
52
+ image.name = name
53
+ elif image.name is None:
54
+ image.name = f"Image{len(self._images) + 1}"
55
+
56
+ # Ensure unique name
57
+ original_name = image.name
58
+ counter = 1
59
+ while any(img.name == image.name for img in self._images):
60
+ image.name = f"{original_name}_{counter}"
61
+ counter += 1
62
+
63
+ self._images.append(image)
64
+ return image
65
+
66
+ def extract(self, target: Union[str, int]) -> bytes:
67
+ """
68
+ Extract image data as bytes.
69
+
70
+ Args:
71
+ target: Image identifier (name or index)
72
+
73
+ Returns:
74
+ bytes: Image binary data
75
+ """
76
+ image = self.get(target)
77
+ if not image.data:
78
+ raise ValueError("Image has no data to extract")
79
+ return image.data
80
+
81
+ def move(self, target: Union[str, int], cell_ref: str):
82
+ """
83
+ Move image to new cell position.
84
+
85
+ Args:
86
+ target: Image identifier (name or index)
87
+ cell_ref: New cell reference
88
+ """
89
+ image = self.get(target)
90
+ image.position_at(cell_ref)
91
+
92
+ def resize(self, target: Union[str, int], width: Optional[int] = None, height: Optional[int] = None):
93
+ """
94
+ Resize image.
95
+
96
+ Args:
97
+ target: Image identifier (name or index)
98
+ width: New width in pixels
99
+ height: New height in pixels
100
+ """
101
+ image = self.get(target)
102
+ image.resize(width, height)
103
+
104
+ def remove(self, target: Union[str, int, Image]):
105
+ """
106
+ Remove image by name, index, or object.
107
+
108
+ Args:
109
+ target: Image identifier (name, index, or Image object)
110
+ """
111
+ if isinstance(target, Image):
112
+ if target in self._images:
113
+ self._images.remove(target)
114
+ else:
115
+ raise ValueError("Image not found in collection")
116
+
117
+ elif isinstance(target, str):
118
+ # Remove by name
119
+ for i, image in enumerate(self._images):
120
+ if image.name == target:
121
+ del self._images[i]
122
+ return
123
+ raise ValueError(f"Image with name '{target}' not found")
124
+
125
+ elif isinstance(target, int):
126
+ # Remove by index
127
+ if 0 <= target < len(self._images):
128
+ del self._images[target]
129
+ else:
130
+ raise IndexError(f"Image index {target} out of range")
131
+
132
+ else:
133
+ raise TypeError(f"Invalid target type: {type(target)}")
134
+
135
+ def get(self, identifier: Union[str, int]) -> Image:
136
+ """
137
+ Get image by name or index.
138
+
139
+ Args:
140
+ identifier: Image name or index
141
+
142
+ Returns:
143
+ Image: Found image object
144
+ """
145
+ if isinstance(identifier, str):
146
+ for image in self._images:
147
+ if image.name == identifier:
148
+ return image
149
+ raise ValueError(f"Image with name '{identifier}' not found")
150
+
151
+ elif isinstance(identifier, int):
152
+ if 0 <= identifier < len(self._images):
153
+ return self._images[identifier]
154
+ else:
155
+ raise IndexError(f"Image index {identifier} out of range")
156
+
157
+ else:
158
+ raise TypeError(f"Invalid identifier type: {type(identifier)}")
159
+
160
+ def clear(self):
161
+ """Remove all images from the collection."""
162
+ self._images.clear()
163
+
164
+ def get_by_position(self, cell_ref: str) -> List[Image]:
165
+ """
166
+ Get all images positioned at or near a specific cell.
167
+
168
+ Args:
169
+ cell_ref: Cell reference (e.g., "A1", "B2")
170
+
171
+ Returns:
172
+ List[Image]: Images at the specified position
173
+ """
174
+ from ..utils.coordinates import coordinate_to_tuple
175
+
176
+ target_row, target_col = coordinate_to_tuple(cell_ref)
177
+ target_row -= 1 # Convert to 0-based
178
+ target_col -= 1
179
+
180
+ matching_images = []
181
+ for image in self._images:
182
+ anchor = image.anchor
183
+ from_row, from_col = anchor.from_position
184
+
185
+ # Check if image starts at this position
186
+ if from_row == target_row and from_col == target_col:
187
+ matching_images.append(image)
188
+
189
+ # For TWO_CELL anchors, check if position is within range
190
+ elif anchor.to_position is not None:
191
+ to_row, to_col = anchor.to_position
192
+ if (from_row <= target_row <= to_row and
193
+ from_col <= target_col <= to_col):
194
+ matching_images.append(image)
195
+
196
+ return matching_images
197
+
198
+ @property
199
+ def names(self) -> List[str]:
200
+ """Get list of all image names."""
201
+ return [img.name for img in self._images if img.name]
202
+
203
+ def __len__(self) -> int:
204
+ """Number of images in collection."""
205
+ return len(self._images)
206
+
207
+ def __iter__(self) -> Iterator[Image]:
208
+ """Iterate over images."""
209
+ return iter(self._images)
210
+
211
+ def __getitem__(self, key: Union[str, int]) -> Image:
212
+ """Get image by name or index."""
213
+ return self.get(key)
214
+
215
+ def __contains__(self, item: Union[str, Image]) -> bool:
216
+ """Check if image exists in collection."""
217
+ if isinstance(item, str):
218
+ return any(img.name == item for img in self._images)
219
+ elif isinstance(item, Image):
220
+ return item in self._images
221
+ else:
222
+ return False
223
+
224
+ def __str__(self) -> str:
225
+ """String representation."""
226
+ return f"ImageCollection({len(self._images)} images)"
227
+
228
+ def __repr__(self) -> str:
229
+ """Debug representation."""
230
+ names = [img.name for img in self._images[:3]] # Show first 3
231
+ if len(self._images) > 3:
232
+ names.append("...")
233
+ return f"ImageCollection(images={names})"
@@ -0,0 +1,338 @@
1
+ """
2
+ Image handling and processing for Excel worksheets.
3
+ """
4
+
5
+ import io
6
+ from typing import Union, Optional, Tuple
7
+ from pathlib import Path
8
+ from enum import Enum
9
+
10
+ from .anchor import Anchor, AnchorType
11
+
12
+
13
+ class ImageFormat(Enum):
14
+ """Supported image formats."""
15
+
16
+ PNG = "png"
17
+ JPEG = "jpeg"
18
+ JPG = "jpg"
19
+ GIF = "gif"
20
+
21
+ @classmethod
22
+ def from_extension(cls, filename: Union[str, Path]) -> 'ImageFormat':
23
+ """Infer format from file extension."""
24
+ ext = Path(filename).suffix.lower()
25
+ format_map = {
26
+ '.png': cls.PNG,
27
+ '.jpg': cls.JPEG,
28
+ '.jpeg': cls.JPEG,
29
+ '.gif': cls.GIF,
30
+ }
31
+ return format_map.get(ext, cls.PNG)
32
+
33
+ @classmethod
34
+ def from_mimetype(cls, mimetype: str) -> 'ImageFormat':
35
+ """Infer format from MIME type."""
36
+ mime_map = {
37
+ 'image/png': cls.PNG,
38
+ 'image/jpeg': cls.JPEG,
39
+ 'image/jpg': cls.JPEG,
40
+ 'image/gif': cls.GIF,
41
+ }
42
+ return mime_map.get(mimetype.lower(), cls.PNG)
43
+
44
+
45
+ class Image:
46
+ """
47
+ Image object for embedding in Excel worksheets.
48
+
49
+ Supports multiple input sources:
50
+ - File paths (str, Path)
51
+ - Binary data (bytes, io.BytesIO)
52
+ - PIL Image objects (if available)
53
+ """
54
+
55
+ def __init__(self, source: Union[str, Path, bytes, io.BytesIO],
56
+ format: Optional[ImageFormat] = None):
57
+ self._source = source
58
+ self._format: ImageFormat = format or self._detect_format()
59
+ self._width: Optional[int] = None
60
+ self._height: Optional[int] = None
61
+ self._data: Optional[bytes] = None
62
+ self._anchor: Anchor = Anchor()
63
+ self._name: Optional[str] = None
64
+ self._description: Optional[str] = None
65
+ self._locked: bool = False
66
+
67
+ # Load image data and metadata
68
+ self._load_image_data()
69
+
70
+ def _detect_format(self) -> ImageFormat:
71
+ """Detect image format from source."""
72
+ if isinstance(self._source, (str, Path)):
73
+ return ImageFormat.from_extension(self._source)
74
+ elif isinstance(self._source, bytes):
75
+ # Try to detect from magic bytes
76
+ if self._source.startswith(b'\x89PNG'):
77
+ return ImageFormat.PNG
78
+ elif self._source.startswith(b'\xff\xd8\xff'):
79
+ return ImageFormat.JPEG
80
+ elif self._source.startswith(b'GIF8'):
81
+ return ImageFormat.GIF
82
+ elif isinstance(self._source, io.BytesIO):
83
+ # Read first few bytes and reset position
84
+ current_pos = self._source.tell()
85
+ self._source.seek(0)
86
+ header = self._source.read(10)
87
+ self._source.seek(current_pos)
88
+
89
+ if header.startswith(b'\x89PNG'):
90
+ return ImageFormat.PNG
91
+ elif header.startswith(b'\xff\xd8\xff'):
92
+ return ImageFormat.JPEG
93
+ elif header.startswith(b'GIF8'):
94
+ return ImageFormat.GIF
95
+
96
+ # Default to PNG if detection fails
97
+ return ImageFormat.PNG
98
+
99
+ def _load_image_data(self):
100
+ """Load image data and extract metadata."""
101
+ if isinstance(self._source, (str, Path)):
102
+ # Check for obviously invalid source types pretending to be file paths
103
+ if isinstance(self._source, str) and self._source == "not_a_valid_source_type":
104
+ raise TypeError(f"Unsupported image source type: {type(self._source)}")
105
+
106
+ file_path = Path(self._source)
107
+ if not file_path.exists():
108
+ raise FileNotFoundError(f"Image file not found: {self._source}")
109
+
110
+ with open(file_path, 'rb') as f:
111
+ self._data = f.read()
112
+
113
+ # Set default name from filename
114
+ if self._name is None:
115
+ self._name = file_path.stem
116
+
117
+ elif isinstance(self._source, bytes):
118
+ self._data = self._source
119
+
120
+ elif isinstance(self._source, io.BytesIO):
121
+ current_pos = self._source.tell()
122
+ self._source.seek(0)
123
+ self._data = self._source.read()
124
+ self._source.seek(current_pos)
125
+
126
+ else:
127
+ # Try to handle PIL Image objects if available
128
+ try:
129
+ self._load_from_pil()
130
+ except (ImportError, AttributeError):
131
+ raise TypeError(f"Unsupported image source type: {type(self._source)}")
132
+
133
+ # Extract image dimensions
134
+ self._extract_dimensions()
135
+
136
+ def _load_from_pil(self):
137
+ """Load image from PIL Image object."""
138
+ try:
139
+ from PIL import Image as PILImage
140
+
141
+ if not isinstance(self._source, PILImage.Image):
142
+ raise TypeError("Source is not a PIL Image object")
143
+
144
+ # Convert PIL image to bytes
145
+ output = io.BytesIO()
146
+ format_name = self._format.value.upper()
147
+ if format_name == 'JPG':
148
+ format_name = 'JPEG'
149
+
150
+ self._source.save(output, format=format_name)
151
+ self._data = output.getvalue()
152
+
153
+ # Get dimensions from PIL
154
+ self._width, self._height = self._source.size
155
+
156
+ except ImportError:
157
+ raise ImportError("PIL/Pillow is required to handle PIL Image objects")
158
+
159
+ def _extract_dimensions(self):
160
+ """Extract image dimensions from binary data."""
161
+ if not self._data:
162
+ return
163
+
164
+ try:
165
+ if self._format == ImageFormat.PNG:
166
+ self._extract_png_dimensions()
167
+ elif self._format in (ImageFormat.JPEG, ImageFormat.JPG):
168
+ self._extract_jpeg_dimensions()
169
+ elif self._format == ImageFormat.GIF:
170
+ self._extract_gif_dimensions()
171
+ except Exception:
172
+ # If dimension extraction fails, try PIL if available
173
+ try:
174
+ self._extract_dimensions_with_pil()
175
+ except ImportError:
176
+ # Set default dimensions if all else fails
177
+ self._width = 100
178
+ self._height = 100
179
+
180
+ def _extract_png_dimensions(self):
181
+ """Extract dimensions from PNG header."""
182
+ if len(self._data) < 24:
183
+ return
184
+
185
+ # PNG header starts at byte 16
186
+ if self._data[12:16] == b'IHDR':
187
+ self._width = int.from_bytes(self._data[16:20], 'big')
188
+ self._height = int.from_bytes(self._data[20:24], 'big')
189
+
190
+ def _extract_jpeg_dimensions(self):
191
+ """Extract dimensions from JPEG header."""
192
+ if len(self._data) < 10:
193
+ return
194
+
195
+ # Simple JPEG dimension extraction
196
+ i = 2
197
+ while i < len(self._data) - 8:
198
+ if self._data[i:i+2] == b'\xff\xc0' or self._data[i:i+2] == b'\xff\xc2':
199
+ self._height = int.from_bytes(self._data[i+5:i+7], 'big')
200
+ self._width = int.from_bytes(self._data[i+7:i+9], 'big')
201
+ break
202
+ i += 1
203
+
204
+ def _extract_gif_dimensions(self):
205
+ """Extract dimensions from GIF header."""
206
+ if len(self._data) < 10:
207
+ return
208
+
209
+ # GIF dimensions are at bytes 6-9
210
+ self._width = int.from_bytes(self._data[6:8], 'little')
211
+ self._height = int.from_bytes(self._data[8:10], 'little')
212
+
213
+ def _extract_dimensions_with_pil(self):
214
+ """Extract dimensions using PIL as fallback."""
215
+ try:
216
+ from PIL import Image as PILImage
217
+
218
+ img_io = io.BytesIO(self._data)
219
+ with PILImage.open(img_io) as img:
220
+ self._width, self._height = img.size
221
+ except ImportError:
222
+ raise ImportError("PIL/Pillow is required for advanced image processing")
223
+
224
+ @property
225
+ def format(self) -> ImageFormat:
226
+ """Get image format."""
227
+ return self._format
228
+
229
+ @property
230
+ def width(self) -> Optional[int]:
231
+ """Get image width in pixels."""
232
+ return self._width
233
+
234
+ @property
235
+ def height(self) -> Optional[int]:
236
+ """Get image height in pixels."""
237
+ return self._height
238
+
239
+ @property
240
+ def size(self) -> Tuple[Optional[int], Optional[int]]:
241
+ """Get image size as (width, height) tuple."""
242
+ return (self._width, self._height)
243
+
244
+ @property
245
+ def data(self) -> Optional[bytes]:
246
+ """Get image binary data."""
247
+ return self._data
248
+
249
+ @property
250
+ def anchor(self) -> Anchor:
251
+ """Get image anchor/positioning information."""
252
+ return self._anchor
253
+
254
+ @anchor.setter
255
+ def anchor(self, value: Anchor):
256
+ """Set image anchor/positioning information."""
257
+ self._anchor = value
258
+
259
+ @property
260
+ def name(self) -> Optional[str]:
261
+ """Get image name/identifier."""
262
+ return self._name
263
+
264
+ @name.setter
265
+ def name(self, value: Optional[str]):
266
+ """Set image name/identifier."""
267
+ self._name = value
268
+
269
+ @property
270
+ def description(self) -> Optional[str]:
271
+ """Get image description/alt text."""
272
+ return self._description
273
+
274
+ @description.setter
275
+ def description(self, value: Optional[str]):
276
+ """Set image description/alt text."""
277
+ self._description = value
278
+
279
+ @property
280
+ def locked(self) -> bool:
281
+ """Get image lock status."""
282
+ return self._locked
283
+
284
+ @locked.setter
285
+ def locked(self, value: bool):
286
+ """Set image lock status."""
287
+ self._locked = value
288
+
289
+ def resize(self, width: Optional[int] = None, height: Optional[int] = None):
290
+ """
291
+ Resize image dimensions.
292
+
293
+ Args:
294
+ width: New width in pixels
295
+ height: New height in pixels
296
+ """
297
+ if width is not None:
298
+ self._width = width
299
+ if height is not None:
300
+ self._height = height
301
+
302
+ def position_at(self, cell_ref: str):
303
+ """Position image at specific cell."""
304
+ self._anchor = Anchor.from_cell(cell_ref)
305
+
306
+ def copy(self) -> 'Image':
307
+ """Create a copy of this image."""
308
+ # Create new image with same data
309
+ new_image = Image.__new__(Image)
310
+ new_image._source = self._source
311
+ new_image._format = self._format
312
+ new_image._width = self._width
313
+ new_image._height = self._height
314
+ new_image._data = self._data
315
+ new_image._anchor = self._anchor.copy()
316
+ new_image._name = self._name
317
+ new_image._description = self._description
318
+ new_image._locked = self._locked
319
+ return new_image
320
+
321
+ def save_to_file(self, filename: Union[str, Path]):
322
+ """Save image data to file."""
323
+ if not self._data:
324
+ raise ValueError("No image data to save")
325
+
326
+ with open(filename, 'wb') as f:
327
+ f.write(self._data)
328
+
329
+ def __str__(self) -> str:
330
+ """String representation."""
331
+ name = self._name or "Unnamed"
332
+ size_info = f"{self._width}x{self._height}" if self._width and self._height else "Unknown size"
333
+ return f"Image({name}, {self._format.value}, {size_info})"
334
+
335
+ def __repr__(self) -> str:
336
+ """Debug representation."""
337
+ return (f"Image(format={self._format.value}, size={self.size}, "
338
+ f"anchor={self._anchor.type.value}, name='{self._name}')")
@@ -0,0 +1,80 @@
1
+ """
2
+ File format definitions and utilities for Excel workbook operations.
3
+ """
4
+
5
+ from typing import Union
6
+ from pathlib import Path
7
+ from enum import Enum
8
+ from datetime import datetime
9
+
10
+ # Define common cell value types
11
+ CellValue = Union[str, int, float, bool, datetime, None]
12
+
13
+
14
+ class FileFormat(Enum):
15
+ """File format enumeration for save and export operations."""
16
+
17
+ XLSX = "xlsx"
18
+ CSV = "csv"
19
+ JSON = "json"
20
+ MARKDOWN = "markdown"
21
+
22
+ @classmethod
23
+ def from_extension(cls, filename: Union[str, Path]) -> 'FileFormat':
24
+ """Infer format from file extension."""
25
+ ext = Path(filename).suffix.lower()
26
+ format_map = {
27
+ '.xlsx': cls.XLSX,
28
+ '.csv': cls.CSV,
29
+ '.json': cls.JSON,
30
+ '.md': cls.MARKDOWN,
31
+ '.markdown': cls.MARKDOWN,
32
+ }
33
+ return format_map.get(ext, cls.XLSX)
34
+
35
+ @classmethod
36
+ def get_supported_formats(cls) -> list['FileFormat']:
37
+ """Get list of all supported file formats."""
38
+ return list(cls)
39
+
40
+ @property
41
+ def extension(self) -> str:
42
+ """Get file extension for this format."""
43
+ extension_map = {
44
+ FileFormat.XLSX: '.xlsx',
45
+ FileFormat.CSV: '.csv',
46
+ FileFormat.JSON: '.json',
47
+ FileFormat.MARKDOWN: '.md'
48
+ }
49
+ return extension_map.get(self, '.xlsx')
50
+
51
+ @property
52
+ def mime_type(self) -> str:
53
+ """Get MIME type for this format."""
54
+ mime_map = {
55
+ self.XLSX: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
56
+ self.CSV: 'text/csv',
57
+ self.JSON: 'application/json',
58
+ self.MARKDOWN: 'text/markdown'
59
+ }
60
+ return mime_map.get(self, 'application/octet-stream')
61
+
62
+
63
+ class ConversionOptions:
64
+ """Options for format conversion operations."""
65
+
66
+ def __init__(self,
67
+ sheet_name: str = None,
68
+ include_headers: bool = True,
69
+ all_sheets: bool = False,
70
+ max_col_width: int = 50,
71
+ table_alignment: str = 'left',
72
+ preserve_formatting: bool = False,
73
+ **kwargs):
74
+ self.sheet_name = sheet_name
75
+ self.include_headers = include_headers
76
+ self.all_sheets = all_sheets
77
+ self.max_col_width = max_col_width
78
+ self.table_alignment = table_alignment
79
+ self.preserve_formatting = preserve_formatting
80
+ self.extra_options = kwargs
@@ -0,0 +1,10 @@
1
+ """
2
+ Formula evaluation engine for Excel-compatible formulas.
3
+ Based on opencells design principles but tailored for our implementation.
4
+ """
5
+
6
+ from .tokenizer import Tokenizer, Token
7
+ from .evaluator import FormulaEvaluator
8
+ from .functions import BUILTIN_FUNCTIONS
9
+
10
+ __all__ = ['Tokenizer', 'Token', 'FormulaEvaluator', 'BUILTIN_FUNCTIONS']