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,340 @@
1
+ """
2
+ Excel Formula Tokenizer - Based on opencells tokenizer
3
+ Converts Excel formulas into token streams for evaluation.
4
+ """
5
+
6
+ import re
7
+ from typing import List, Optional
8
+
9
+
10
+ class Token:
11
+ """Represents a single token in a formula."""
12
+
13
+ # Token types
14
+ LITERAL = "LITERAL"
15
+ OPERAND = "OPERAND"
16
+ FUNCTION = "FUNCTION"
17
+ SUBEXPR = "SUBEXPR"
18
+ ARGUMENT = "ARGUMENT"
19
+ OPERATOR = "OPERATOR"
20
+ WHITESPACE = "WHITESPACE"
21
+ ERROR = "ERROR"
22
+
23
+ # Token subtypes
24
+ TEXT = "TEXT"
25
+ NUMBER = "NUMBER"
26
+ LOGICAL = "LOGICAL"
27
+ RANGE = "RANGE"
28
+ REFERENCE = "REFERENCE"
29
+ NAME = "NAME"
30
+
31
+ # Operator types
32
+ MATH = "MATH"
33
+ CONCAT = "CONCAT"
34
+ INTERSECT = "INTERSECT"
35
+ UNION = "UNION"
36
+
37
+ def __init__(self, value: str, type_: str, subtype: str = ""):
38
+ self.value = value
39
+ self.type = type_
40
+ self.subtype = subtype
41
+
42
+ def __repr__(self):
43
+ return f"Token({self.value!r}, {self.type}, {self.subtype})"
44
+
45
+
46
+ class Tokenizer:
47
+ """Tokenizer for Excel formulas."""
48
+
49
+ # Regex patterns
50
+ CELL_REF_PATTERN = re.compile(r'^(\$?)([A-Z]+)(\$?)(\d+)$')
51
+ RANGE_PATTERN = re.compile(r'^(\$?[A-Z]+\$?\d+):(\$?[A-Z]+\$?\d+)$')
52
+ FUNCTION_PATTERN = re.compile(r'^[A-Z_][A-Z0-9_.]*$')
53
+ NUMBER_PATTERN = re.compile(r'^-?\d+(\.\d*)?([Ee][+-]?\d+)?$|^-?\d*\.\d+([Ee][+-]?\d+)?$')
54
+
55
+ # Excel error codes
56
+ ERROR_CODES = {'#NULL!', '#DIV/0!', '#VALUE!', '#REF!', '#NAME?', '#NUM!', '#N/A'}
57
+
58
+ def __init__(self, formula: str):
59
+ self.formula = formula.strip()
60
+ self.tokens: List[Token] = []
61
+ self.position = 0
62
+ self._tokenize()
63
+
64
+ def _tokenize(self):
65
+ """Parse the formula into tokens."""
66
+ if not self.formula:
67
+ return
68
+
69
+ # Skip leading = if present
70
+ if self.formula.startswith('='):
71
+ self.position = 1
72
+
73
+ while self.position < len(self.formula):
74
+ self._skip_whitespace()
75
+ if self.position >= len(self.formula):
76
+ break
77
+
78
+ if self._try_string():
79
+ continue
80
+ elif self._try_number():
81
+ continue
82
+ elif self._try_operator():
83
+ continue
84
+ elif self._try_function():
85
+ continue
86
+ elif self._try_reference():
87
+ continue
88
+ elif self._try_error():
89
+ continue
90
+ elif self._try_parenthesis():
91
+ continue
92
+ elif self._try_separator():
93
+ continue
94
+ else:
95
+ # Unknown character, treat as text
96
+ self._consume_text()
97
+
98
+ def _current_char(self) -> Optional[str]:
99
+ """Get current character."""
100
+ if self.position < len(self.formula):
101
+ return self.formula[self.position]
102
+ return None
103
+
104
+ def _peek_char(self, offset: int = 1) -> Optional[str]:
105
+ """Peek ahead at character."""
106
+ pos = self.position + offset
107
+ if pos < len(self.formula):
108
+ return self.formula[pos]
109
+ return None
110
+
111
+ def _skip_whitespace(self):
112
+ """Skip whitespace characters."""
113
+ while self.position < len(self.formula) and self.formula[self.position].isspace():
114
+ self.position += 1
115
+
116
+ def _try_string(self) -> bool:
117
+ """Try to parse a quoted string."""
118
+ char = self._current_char()
119
+ if char not in ('"', "'"):
120
+ return False
121
+
122
+ quote_char = char
123
+ start_pos = self.position
124
+ self.position += 1 # Skip opening quote
125
+ value = ''
126
+
127
+ while self.position < len(self.formula):
128
+ char = self._current_char()
129
+ if char == quote_char:
130
+ # Check for escaped quote (doubled quotes)
131
+ if self._peek_char() == quote_char:
132
+ value += quote_char
133
+ self.position += 2
134
+ else:
135
+ self.position += 1 # Skip closing quote
136
+ break
137
+ else:
138
+ value += char
139
+ self.position += 1
140
+
141
+ self.tokens.append(Token(value, Token.OPERAND, Token.TEXT))
142
+ return True
143
+
144
+ def _try_number(self) -> bool:
145
+ """Try to parse a number."""
146
+ start_pos = self.position
147
+ value = ''
148
+
149
+ # Only handle negative sign if it's at the start or after an operator/opening paren
150
+ can_be_negative = (
151
+ len(self.tokens) == 0 or # Start of formula
152
+ self.tokens[-1].type in (Token.OPERATOR, Token.SUBEXPR, Token.ARGUMENT)
153
+ )
154
+
155
+ # Handle negative sign only in valid contexts
156
+ if self._current_char() == '-' and can_be_negative:
157
+ value += '-'
158
+ self.position += 1
159
+ elif self._current_char() == '-':
160
+ # It's likely a subtraction operator, not a negative number
161
+ return False
162
+
163
+ # Collect digits and decimal points
164
+ while self.position < len(self.formula):
165
+ char = self._current_char()
166
+ if char.isdigit() or char == '.':
167
+ value += char
168
+ self.position += 1
169
+ elif char in 'Ee' and value and value[-1].isdigit():
170
+ # Scientific notation
171
+ value += char
172
+ self.position += 1
173
+ # Handle optional +/- after E
174
+ if self._current_char() in '+-':
175
+ value += self._current_char()
176
+ self.position += 1
177
+ else:
178
+ break
179
+
180
+ if value and self.NUMBER_PATTERN.match(value):
181
+ self.tokens.append(Token(value, Token.OPERAND, Token.NUMBER))
182
+ return True
183
+ else:
184
+ # Not a valid number, reset position
185
+ self.position = start_pos
186
+ return False
187
+
188
+ def _try_operator(self) -> bool:
189
+ """Try to parse an operator."""
190
+ char = self._current_char()
191
+ operators = {
192
+ '+': (Token.OPERATOR, Token.MATH),
193
+ '-': (Token.OPERATOR, Token.MATH),
194
+ '*': (Token.OPERATOR, Token.MATH),
195
+ '/': (Token.OPERATOR, Token.MATH),
196
+ '^': (Token.OPERATOR, Token.MATH),
197
+ '&': (Token.OPERATOR, Token.CONCAT),
198
+ '=': (Token.OPERATOR, Token.MATH),
199
+ '<': (Token.OPERATOR, Token.MATH),
200
+ '>': (Token.OPERATOR, Token.MATH),
201
+ '%': (Token.OPERATOR, Token.MATH),
202
+ }
203
+
204
+ if char in operators:
205
+ # Check for multi-character operators
206
+ next_char = self._peek_char()
207
+ if char == '<' and next_char == '>':
208
+ self.tokens.append(Token('<>', Token.OPERATOR, Token.MATH))
209
+ self.position += 2
210
+ elif char == '<' and next_char == '=':
211
+ self.tokens.append(Token('<=', Token.OPERATOR, Token.MATH))
212
+ self.position += 2
213
+ elif char == '>' and next_char == '=':
214
+ self.tokens.append(Token('>=', Token.OPERATOR, Token.MATH))
215
+ self.position += 2
216
+ else:
217
+ type_, subtype = operators[char]
218
+ self.tokens.append(Token(char, type_, subtype))
219
+ self.position += 1
220
+ return True
221
+ return False
222
+
223
+ def _try_function(self) -> bool:
224
+ """Try to parse a function name."""
225
+ start_pos = self.position
226
+ value = ''
227
+
228
+ # Functions start with letter or underscore
229
+ char = self._current_char()
230
+ if not (char.isalpha() or char == '_'):
231
+ return False
232
+
233
+ # Collect function name
234
+ while self.position < len(self.formula):
235
+ char = self._current_char()
236
+ if char.isalnum() or char in '_.':
237
+ value += char
238
+ self.position += 1
239
+ else:
240
+ break
241
+
242
+ # Check if followed by opening parenthesis
243
+ self._skip_whitespace()
244
+ if self._current_char() == '(':
245
+ if self.FUNCTION_PATTERN.match(value.upper()):
246
+ self.tokens.append(Token(value.upper(), Token.FUNCTION))
247
+ return True
248
+
249
+ # Not a function, reset position
250
+ self.position = start_pos
251
+ return False
252
+
253
+ def _try_reference(self) -> bool:
254
+ """Try to parse a cell reference or range."""
255
+ start_pos = self.position
256
+ value = ''
257
+
258
+ # Collect potential reference
259
+ while self.position < len(self.formula):
260
+ char = self._current_char()
261
+ if char.isalnum() or char in '$:':
262
+ value += char
263
+ self.position += 1
264
+ else:
265
+ break
266
+
267
+ if value:
268
+ # Check for range (contains colon)
269
+ if ':' in value and self.RANGE_PATTERN.match(value.upper()):
270
+ self.tokens.append(Token(value.upper(), Token.OPERAND, Token.RANGE))
271
+ return True
272
+ # Check for single cell reference
273
+ elif self.CELL_REF_PATTERN.match(value.upper()):
274
+ self.tokens.append(Token(value.upper(), Token.OPERAND, Token.REFERENCE))
275
+ return True
276
+
277
+ # Not a reference, reset position
278
+ self.position = start_pos
279
+ return False
280
+
281
+ def _try_error(self) -> bool:
282
+ """Try to parse an error value."""
283
+ start_pos = self.position
284
+ if self._current_char() != '#':
285
+ return False
286
+
287
+ value = ''
288
+ while self.position < len(self.formula):
289
+ char = self._current_char()
290
+ if char.isalnum() or char in '#!/?':
291
+ value += char
292
+ self.position += 1
293
+ else:
294
+ break
295
+
296
+ if value in self.ERROR_CODES:
297
+ self.tokens.append(Token(value, Token.OPERAND, Token.ERROR))
298
+ return True
299
+ else:
300
+ self.position = start_pos
301
+ return False
302
+
303
+ def _try_parenthesis(self) -> bool:
304
+ """Try to parse parentheses."""
305
+ char = self._current_char()
306
+ if char == '(':
307
+ self.tokens.append(Token('(', Token.SUBEXPR, "OPEN"))
308
+ self.position += 1
309
+ return True
310
+ elif char == ')':
311
+ self.tokens.append(Token(')', Token.SUBEXPR, "CLOSE"))
312
+ self.position += 1
313
+ return True
314
+ return False
315
+
316
+ def _try_separator(self) -> bool:
317
+ """Try to parse separators (comma, semicolon)."""
318
+ char = self._current_char()
319
+ if char in (',', ';'):
320
+ self.tokens.append(Token(char, Token.ARGUMENT))
321
+ self.position += 1
322
+ return True
323
+ return False
324
+
325
+ def _consume_text(self):
326
+ """Consume remaining text as literal."""
327
+ value = ''
328
+ while self.position < len(self.formula):
329
+ char = self._current_char()
330
+ if char.isspace() or char in '()+-*/^&=<>%,;':
331
+ break
332
+ value += char
333
+ self.position += 1
334
+
335
+ if value:
336
+ self.tokens.append(Token(value, Token.OPERAND, Token.TEXT))
337
+
338
+ def __iter__(self):
339
+ """Iterate over tokens."""
340
+ return iter(self.tokens)
@@ -0,0 +1,27 @@
1
+ """
2
+ I/O module for Excel file reading and writing with unified format support.
3
+ """
4
+
5
+ # Format-specific readers and writers
6
+ from .csv import CsvReader, CsvWriter
7
+ from .json import JsonReader, JsonWriter
8
+ from .md import MarkdownReader, MarkdownWriter
9
+ from .xlsx import XlsxReader, XlsxWriter
10
+
11
+ # Unified architecture components
12
+ from .models import WorkbookData
13
+ from .interfaces import IFormatHandler
14
+ from .factory import FormatHandlerFactory
15
+
16
+ __all__ = [
17
+ # Format-specific components
18
+ "CsvReader", "CsvWriter",
19
+ "JsonReader", "JsonWriter",
20
+ "MarkdownReader", "MarkdownWriter",
21
+ "XlsxReader", "XlsxWriter",
22
+
23
+ # Unified architecture components
24
+ "WorkbookData",
25
+ "IFormatHandler",
26
+ "FormatHandlerFactory"
27
+ ]
@@ -0,0 +1,8 @@
1
+ """
2
+ CSV I/O operations.
3
+ """
4
+
5
+ from .reader import CsvReader
6
+ from .writer import CsvWriter
7
+
8
+ __all__ = ["CsvReader", "CsvWriter"]
@@ -0,0 +1,88 @@
1
+ """
2
+ CSV file reader for loading CSV data into workbook format.
3
+ """
4
+
5
+ import csv
6
+ from typing import Dict, List, Optional, Union, TYPE_CHECKING
7
+ from pathlib import Path
8
+ from ...formats import CellValue
9
+
10
+ if TYPE_CHECKING:
11
+ from ...workbook import Workbook
12
+
13
+
14
+ class CsvReader:
15
+ """Reader for CSV files."""
16
+
17
+ def __init__(self):
18
+ pass
19
+
20
+ def read(self, file_path: str, **kwargs) -> List[List[CellValue]]:
21
+ """Read CSV file and return data as list of rows."""
22
+ delimiter = kwargs.get('delimiter', ',')
23
+ quotechar = kwargs.get('quotechar', '"')
24
+ encoding = kwargs.get('encoding', 'utf-8')
25
+ has_header = kwargs.get('has_header', False)
26
+
27
+ try:
28
+ with open(file_path, 'r', encoding=encoding, newline='') as file:
29
+ reader = csv.reader(file, delimiter=delimiter, quotechar=quotechar)
30
+
31
+ data = []
32
+ for row in reader:
33
+ # Convert each cell value
34
+ converted_row = []
35
+ for cell in row:
36
+ converted_row.append(self._convert_cell_value(cell))
37
+ data.append(converted_row)
38
+
39
+ return data
40
+
41
+ except FileNotFoundError:
42
+ raise FileNotFoundError(f"CSV file not found: {file_path}")
43
+ except Exception as e:
44
+ raise ValueError(f"Error reading CSV file: {e}")
45
+
46
+ def _convert_cell_value(self, value: str) -> CellValue:
47
+ """Convert string value to appropriate Python type."""
48
+ if not value or value.strip() == "":
49
+ return None
50
+
51
+ value = value.strip()
52
+
53
+ # Try boolean first
54
+ if value.upper() in ('TRUE', 'FALSE'):
55
+ return value.upper() == 'TRUE'
56
+
57
+ # Try integer
58
+ try:
59
+ if '.' not in value and 'e' not in value.lower():
60
+ return int(value)
61
+ except ValueError:
62
+ pass
63
+
64
+ # Try float
65
+ try:
66
+ return float(value)
67
+ except ValueError:
68
+ pass
69
+
70
+ # Return as string
71
+ return value
72
+
73
+ def load_workbook(self, workbook: 'Workbook', file_path: str, **options) -> None:
74
+ """Load CSV file into workbook object."""
75
+ data = self.read(file_path, **options)
76
+
77
+ # Clear existing worksheets
78
+ workbook._worksheets.clear()
79
+ workbook._active_sheet = None
80
+
81
+ # Create single worksheet
82
+ worksheet = workbook.create_sheet("Sheet1")
83
+
84
+ # Populate worksheet with CSV data
85
+ for row_idx, row_data in enumerate(data, 1):
86
+ for col_idx, cell_value in enumerate(row_data, 1):
87
+ if cell_value is not None:
88
+ worksheet.cell(row_idx, col_idx, cell_value)
@@ -0,0 +1,98 @@
1
+ """
2
+ CSV file writer for saving workbook data to CSV format.
3
+ """
4
+
5
+ import csv
6
+ import io
7
+ from typing import List, Optional, TYPE_CHECKING
8
+ from ...formats import CellValue
9
+
10
+ if TYPE_CHECKING:
11
+ from ...workbook import Workbook
12
+ from ...worksheet import Worksheet
13
+
14
+
15
+ class CsvWriter:
16
+ """Writer for CSV files."""
17
+
18
+ def __init__(self):
19
+ pass
20
+
21
+ def write(self, file_path: str, data: List[List[CellValue]], **kwargs) -> None:
22
+ """Write data to CSV file."""
23
+ delimiter = kwargs.get('delimiter', ',')
24
+ quotechar = kwargs.get('quotechar', '"')
25
+ encoding = kwargs.get('encoding', 'utf-8')
26
+
27
+ try:
28
+ with open(file_path, 'w', newline='', encoding=encoding) as file:
29
+ writer = csv.writer(file, delimiter=delimiter, quotechar=quotechar,
30
+ quoting=csv.QUOTE_MINIMAL)
31
+
32
+ for row in data:
33
+ formatted_row = []
34
+ for cell in row:
35
+ formatted_row.append(self._format_cell_value(cell))
36
+ writer.writerow(formatted_row)
37
+
38
+ except Exception as e:
39
+ raise ValueError(f"Error writing CSV file: {e}")
40
+
41
+ def write_workbook(self, file_path: str, workbook: 'Workbook', **kwargs) -> None:
42
+ """Write workbook data to CSV file."""
43
+ sheet_name = kwargs.get('sheet_name')
44
+
45
+ # Get target worksheet
46
+ if sheet_name and sheet_name in workbook._worksheets:
47
+ worksheet = workbook._worksheets[sheet_name]
48
+ else:
49
+ worksheet = workbook.active
50
+
51
+ if not worksheet or not worksheet._cells:
52
+ # Write empty file
53
+ with open(file_path, 'w', newline='', encoding=kwargs.get('encoding', 'utf-8')) as file:
54
+ pass
55
+ return
56
+
57
+ # Convert worksheet to data
58
+ data = self._worksheet_to_data(worksheet)
59
+ self.write(file_path, data, **kwargs)
60
+
61
+ def _worksheet_to_data(self, worksheet: 'Worksheet') -> List[List[CellValue]]:
62
+ """Convert worksheet to list of rows."""
63
+ max_row = worksheet.max_row
64
+ max_col = worksheet.max_column
65
+
66
+ if max_row == 0 or max_col == 0:
67
+ return []
68
+
69
+ data = []
70
+ for row in range(1, max_row + 1):
71
+ row_data = []
72
+ for col in range(1, max_col + 1):
73
+ cell = worksheet._cells.get((row, col))
74
+ if cell and cell.value is not None:
75
+ row_data.append(cell.value)
76
+ else:
77
+ row_data.append(None)
78
+
79
+ # Skip completely empty rows unless they're in the middle
80
+ if any(val is not None for val in row_data) or row < max_row:
81
+ data.append(row_data)
82
+
83
+ return data
84
+
85
+ def _format_cell_value(self, value: CellValue) -> str:
86
+ """Format cell value for CSV output."""
87
+ if value is None:
88
+ return ""
89
+ elif isinstance(value, bool):
90
+ return "TRUE" if value else "FALSE"
91
+ elif isinstance(value, (int, float)):
92
+ return str(value)
93
+ else:
94
+ return str(value)
95
+
96
+ def save_workbook(self, workbook: 'Workbook', file_path: str, **options) -> None:
97
+ """Save workbook to CSV file - unified interface method."""
98
+ self.write_workbook(file_path, workbook, **options)
@@ -0,0 +1,138 @@
1
+ """
2
+ Format handler factory for unified file processing.
3
+ """
4
+
5
+ from typing import Dict, Optional, Type, List
6
+ from pathlib import Path
7
+ from .interfaces import IFormatHandler
8
+
9
+
10
+ class FormatHandlerFactory:
11
+ """Factory for managing format handlers."""
12
+
13
+ _handlers: Dict[str, Type[IFormatHandler]] = {}
14
+ _instances: Dict[str, IFormatHandler] = {}
15
+
16
+ @classmethod
17
+ def register(cls, extension: str, handler_class: Type[IFormatHandler]) -> None:
18
+ """Register format handler for file extension."""
19
+ if not extension.startswith('.'):
20
+ extension = '.' + extension
21
+ cls._handlers[extension.lower()] = handler_class
22
+ # Clear instance cache when registering new handler
23
+ if extension.lower() in cls._instances:
24
+ del cls._instances[extension.lower()]
25
+
26
+ @classmethod
27
+ def get_handler(cls, file_path: str) -> Optional[IFormatHandler]:
28
+ """Get format handler for file extension."""
29
+ ext = Path(file_path).suffix.lower()
30
+
31
+ if ext not in cls._handlers:
32
+ return None
33
+
34
+ # Use singleton pattern for handler instances
35
+ if ext not in cls._instances:
36
+ cls._instances[ext] = cls._handlers[ext]()
37
+
38
+ return cls._instances[ext]
39
+
40
+ @classmethod
41
+ def get_supported_formats(cls) -> List[str]:
42
+ """Get list of supported file extensions."""
43
+ return list(cls._handlers.keys())
44
+
45
+ @classmethod
46
+ def is_supported(cls, file_path: str) -> bool:
47
+ """Check if file format is supported."""
48
+ ext = Path(file_path).suffix.lower()
49
+ return ext in cls._handlers
50
+
51
+ @classmethod
52
+ def clear_cache(cls) -> None:
53
+ """Clear handler instance cache."""
54
+ cls._instances.clear()
55
+
56
+
57
+ def _register_builtin_formats():
58
+ """Register built-in format handlers."""
59
+
60
+ # XLSX Handler
61
+ class XlsxHandler(IFormatHandler):
62
+ """Handler for XLSX format."""
63
+
64
+ def __init__(self):
65
+ from .xlsx.reader import XlsxReader
66
+ from .xlsx.writer import XlsxWriter
67
+ self._reader = XlsxReader()
68
+ self._writer = XlsxWriter()
69
+
70
+ def load_workbook(self, workbook, file_path: str, **options):
71
+ return self._reader.load_workbook(workbook, file_path, **options)
72
+
73
+ def save_workbook(self, workbook, file_path: str, **options):
74
+ return self._writer.save_workbook(workbook, file_path, **options)
75
+
76
+ FormatHandlerFactory.register('.xlsx', XlsxHandler)
77
+ FormatHandlerFactory.register('.xlsm', XlsxHandler)
78
+ FormatHandlerFactory.register('.xltx', XlsxHandler)
79
+ FormatHandlerFactory.register('.xltm', XlsxHandler)
80
+
81
+ # JSON Handler
82
+ class JsonHandler(IFormatHandler):
83
+ """Handler for JSON format."""
84
+
85
+ def __init__(self):
86
+ from .json.reader import JsonReader
87
+ from .json.writer import JsonWriter
88
+ self._reader = JsonReader()
89
+ self._writer = JsonWriter()
90
+
91
+ def load_workbook(self, workbook, file_path: str, **options):
92
+ return self._reader.load_workbook(workbook, file_path, **options)
93
+
94
+ def save_workbook(self, workbook, file_path: str, **options):
95
+ return self._writer.save_workbook(workbook, file_path, **options)
96
+
97
+ FormatHandlerFactory.register('.json', JsonHandler)
98
+
99
+ # CSV Handler
100
+ class CsvHandler(IFormatHandler):
101
+ """Handler for CSV format."""
102
+
103
+ def __init__(self):
104
+ from .csv.reader import CsvReader
105
+ from .csv.writer import CsvWriter
106
+ self._reader = CsvReader()
107
+ self._writer = CsvWriter()
108
+
109
+ def load_workbook(self, workbook, file_path: str, **options):
110
+ return self._reader.load_workbook(workbook, file_path, **options)
111
+
112
+ def save_workbook(self, workbook, file_path: str, **options):
113
+ return self._writer.save_workbook(workbook, file_path, **options)
114
+
115
+ FormatHandlerFactory.register('.csv', CsvHandler)
116
+
117
+ # Markdown Handler
118
+ class MarkdownHandler(IFormatHandler):
119
+ """Handler for Markdown format."""
120
+
121
+ def __init__(self):
122
+ from .md.reader import MarkdownReader
123
+ from .md.writer import MarkdownWriter
124
+ self._reader = MarkdownReader()
125
+ self._writer = MarkdownWriter()
126
+
127
+ def load_workbook(self, workbook, file_path: str, **options):
128
+ return self._reader.load_workbook(workbook, file_path, **options)
129
+
130
+ def save_workbook(self, workbook, file_path: str, **options):
131
+ return self._writer.save_workbook(workbook, file_path, **options)
132
+
133
+ FormatHandlerFactory.register('.md', MarkdownHandler)
134
+ FormatHandlerFactory.register('.markdown', MarkdownHandler)
135
+
136
+
137
+ # Initialize built-in formats
138
+ _register_builtin_formats()