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,360 @@
1
+ """
2
+ Formula Evaluator - Evaluates Excel formulas using tokens and functions.
3
+ """
4
+
5
+ import re
6
+ from typing import Any, Dict, List, Union, Optional, TYPE_CHECKING
7
+ from .tokenizer import Tokenizer, Token
8
+ from .functions import BUILTIN_FUNCTIONS, ExcelError, ValueErrorExcel, DivisionByZeroError
9
+
10
+ if TYPE_CHECKING:
11
+ from ..worksheet import Worksheet
12
+
13
+
14
+ class CircularReferenceError(ExcelError):
15
+ """Circular reference detected."""
16
+ def __str__(self):
17
+ return "#CIRCULAR!"
18
+
19
+
20
+ class FormulaEvaluator:
21
+ """Evaluates Excel formulas."""
22
+
23
+ def __init__(self, worksheet: Optional['Worksheet'] = None):
24
+ self.worksheet = worksheet
25
+ self._evaluation_stack = set() # Track cells being evaluated to detect circular references
26
+
27
+ def evaluate(self, formula: str, cell_address: Optional[str] = None) -> Any:
28
+ """
29
+ Evaluate a formula and return the result.
30
+
31
+ Args:
32
+ formula: The formula to evaluate (with or without = prefix)
33
+ cell_address: Address of the cell containing this formula (for circular ref detection)
34
+
35
+ Returns:
36
+ The evaluated result
37
+ """
38
+ if not formula:
39
+ return ""
40
+
41
+ # Remove leading = if present
42
+ if formula.startswith('='):
43
+ formula = formula[1:]
44
+
45
+ if not formula.strip():
46
+ return ""
47
+
48
+ # Check for circular references
49
+ if cell_address and cell_address in self._evaluation_stack:
50
+ raise CircularReferenceError()
51
+
52
+ try:
53
+ if cell_address:
54
+ self._evaluation_stack.add(cell_address)
55
+
56
+ # Tokenize the formula
57
+ tokenizer = Tokenizer('=' + formula)
58
+ tokens = list(tokenizer)
59
+
60
+ if not tokens:
61
+ return ""
62
+
63
+ # Evaluate the token stream
64
+ result = self._evaluate_tokens(tokens)
65
+ return result
66
+
67
+ except ExcelError:
68
+ raise
69
+ except Exception as e:
70
+ raise ValueErrorExcel() from e
71
+ finally:
72
+ if cell_address:
73
+ self._evaluation_stack.discard(cell_address)
74
+
75
+ def _evaluate_tokens(self, tokens: List[Token]) -> Any:
76
+ """Evaluate a list of tokens."""
77
+ if not tokens:
78
+ return ""
79
+
80
+ # Simple expression evaluation using shunting yard algorithm
81
+ output_queue = []
82
+ operator_stack = []
83
+
84
+ i = 0
85
+ while i < len(tokens):
86
+ token = tokens[i]
87
+
88
+ if token.type == Token.OPERAND:
89
+ output_queue.append(self._evaluate_operand(token))
90
+
91
+ elif token.type == Token.FUNCTION:
92
+ # Find matching closing parenthesis
93
+ func_name = token.value
94
+ if i + 1 < len(tokens) and tokens[i + 1].type == Token.SUBEXPR and tokens[i + 1].subtype == "OPEN":
95
+ args_start = i + 2
96
+ args_end = self._find_matching_paren(tokens, i + 1)
97
+
98
+ # Extract and evaluate arguments
99
+ args_tokens = tokens[args_start:args_end]
100
+ args = self._evaluate_function_args(args_tokens)
101
+
102
+ # Call the function
103
+ result = self._call_function(func_name, args)
104
+ output_queue.append(result)
105
+
106
+ i = args_end # Skip to after closing paren
107
+ else:
108
+ # Function without parentheses (like PI)
109
+ result = self._call_function(func_name, [])
110
+ output_queue.append(result)
111
+
112
+ elif token.type == Token.OPERATOR:
113
+ # Handle operators
114
+ while (operator_stack and
115
+ operator_stack[-1].type == Token.OPERATOR and
116
+ self._precedence(operator_stack[-1]) >= self._precedence(token)):
117
+ op = operator_stack.pop()
118
+ right = output_queue.pop() if output_queue else 0
119
+ left = output_queue.pop() if output_queue else 0
120
+ result = self._apply_operator(op, left, right)
121
+ output_queue.append(result)
122
+ operator_stack.append(token)
123
+
124
+ elif token.type == Token.SUBEXPR:
125
+ if token.subtype == "OPEN":
126
+ operator_stack.append(token)
127
+ elif token.subtype == "CLOSE":
128
+ while (operator_stack and
129
+ operator_stack[-1].type != Token.SUBEXPR):
130
+ op = operator_stack.pop()
131
+ right = output_queue.pop() if output_queue else 0
132
+ left = output_queue.pop() if output_queue else 0
133
+ result = self._apply_operator(op, left, right)
134
+ output_queue.append(result)
135
+ if operator_stack:
136
+ operator_stack.pop() # Remove opening paren
137
+
138
+ i += 1
139
+
140
+ # Process remaining operators
141
+ while operator_stack:
142
+ op = operator_stack.pop()
143
+ if op.type == Token.OPERATOR:
144
+ right = output_queue.pop() if output_queue else 0
145
+ left = output_queue.pop() if output_queue else 0
146
+ result = self._apply_operator(op, left, right)
147
+ output_queue.append(result)
148
+
149
+ return output_queue[0] if output_queue else ""
150
+
151
+ def _evaluate_operand(self, token: Token) -> Any:
152
+ """Evaluate a single operand token."""
153
+ if token.subtype == Token.NUMBER:
154
+ try:
155
+ return int(token.value) if '.' not in token.value else float(token.value)
156
+ except ValueError:
157
+ return 0
158
+
159
+ elif token.subtype == Token.TEXT:
160
+ return token.value
161
+
162
+ elif token.subtype == Token.REFERENCE:
163
+ # Cell reference like A1, B2
164
+ return self._get_cell_value(token.value)
165
+
166
+ elif token.subtype == Token.RANGE:
167
+ # Range like A1:B2
168
+ return self._get_range_values(token.value)
169
+
170
+ elif token.subtype == Token.LOGICAL:
171
+ return token.value.upper() == "TRUE"
172
+
173
+ elif token.subtype == Token.ERROR:
174
+ raise ValueErrorExcel()
175
+
176
+ else:
177
+ return token.value
178
+
179
+ def _get_cell_value(self, cell_ref: str) -> Any:
180
+ """Get value from a cell reference."""
181
+ if not self.worksheet:
182
+ return 0
183
+
184
+ # Parse cell reference (e.g., A1, $B$2)
185
+ match = re.match(r'(\$?)([A-Z]+)(\$?)(\d+)', cell_ref)
186
+ if not match:
187
+ return 0
188
+
189
+ col_letters = match.group(2)
190
+ row_num = int(match.group(4))
191
+
192
+ # Convert column letters to number
193
+ col_num = 0
194
+ for i, letter in enumerate(reversed(col_letters)):
195
+ col_num += (ord(letter) - ord('A') + 1) * (26 ** i)
196
+
197
+ # Get cell from worksheet
198
+ cell = self.worksheet._cells.get((row_num, col_num))
199
+ if not cell:
200
+ return 0
201
+
202
+ # If it's a formula, evaluate it recursively
203
+ if cell.is_formula():
204
+ try:
205
+ return self.evaluate(cell.formula, cell_ref)
206
+ except CircularReferenceError:
207
+ return "#CIRCULAR!"
208
+
209
+ return cell.value if cell.value is not None else 0
210
+
211
+ def _get_range_values(self, range_ref: str) -> List[Any]:
212
+ """Get values from a range reference."""
213
+ if ':' not in range_ref:
214
+ return [self._get_cell_value(range_ref)]
215
+
216
+ start_ref, end_ref = range_ref.split(':')
217
+
218
+ # Parse start and end references
219
+ start_match = re.match(r'(\$?)([A-Z]+)(\$?)(\d+)', start_ref)
220
+ end_match = re.match(r'(\$?)([A-Z]+)(\$?)(\d+)', end_ref)
221
+
222
+ if not start_match or not end_match:
223
+ return []
224
+
225
+ # Convert to column/row numbers
226
+ start_col = sum((ord(c) - ord('A') + 1) * (26 ** i)
227
+ for i, c in enumerate(reversed(start_match.group(2))))
228
+ start_row = int(start_match.group(4))
229
+
230
+ end_col = sum((ord(c) - ord('A') + 1) * (26 ** i)
231
+ for i, c in enumerate(reversed(end_match.group(2))))
232
+ end_row = int(end_match.group(4))
233
+
234
+ # Collect values from range
235
+ values = []
236
+ for row in range(min(start_row, end_row), max(start_row, end_row) + 1):
237
+ for col in range(min(start_col, end_col), max(start_col, end_col) + 1):
238
+ cell_ref = f"{self._col_num_to_letter(col)}{row}"
239
+ values.append(self._get_cell_value(cell_ref))
240
+
241
+ return values
242
+
243
+ def _col_num_to_letter(self, col_num: int) -> str:
244
+ """Convert column number to letter."""
245
+ result = ""
246
+ while col_num > 0:
247
+ col_num -= 1
248
+ result = chr(col_num % 26 + ord('A')) + result
249
+ col_num //= 26
250
+ return result
251
+
252
+ def _find_matching_paren(self, tokens: List[Token], start_pos: int) -> int:
253
+ """Find the matching closing parenthesis."""
254
+ paren_count = 1
255
+ pos = start_pos + 1
256
+
257
+ while pos < len(tokens) and paren_count > 0:
258
+ token = tokens[pos]
259
+ if token.type == Token.SUBEXPR:
260
+ if token.subtype == "OPEN":
261
+ paren_count += 1
262
+ elif token.subtype == "CLOSE":
263
+ paren_count -= 1
264
+ pos += 1
265
+
266
+ return pos - 1 # Position of closing paren
267
+
268
+ def _evaluate_function_args(self, tokens: List[Token]) -> List[Any]:
269
+ """Evaluate function arguments."""
270
+ if not tokens:
271
+ return []
272
+
273
+ args = []
274
+ current_arg = []
275
+ paren_depth = 0
276
+
277
+ for token in tokens:
278
+ if token.type == Token.ARGUMENT and paren_depth == 0:
279
+ # End of current argument
280
+ if current_arg:
281
+ arg_result = self._evaluate_tokens(current_arg)
282
+ args.append(arg_result)
283
+ current_arg = []
284
+ else:
285
+ if token.type == Token.SUBEXPR:
286
+ if token.subtype == "OPEN":
287
+ paren_depth += 1
288
+ elif token.subtype == "CLOSE":
289
+ paren_depth -= 1
290
+ current_arg.append(token)
291
+
292
+ # Add final argument
293
+ if current_arg:
294
+ arg_result = self._evaluate_tokens(current_arg)
295
+ args.append(arg_result)
296
+
297
+ return args
298
+
299
+ def _call_function(self, func_name: str, args: List[Any]) -> Any:
300
+ """Call a built-in function."""
301
+ if func_name in BUILTIN_FUNCTIONS:
302
+ func = BUILTIN_FUNCTIONS[func_name]
303
+ try:
304
+ return func(*args)
305
+ except Exception as e:
306
+ if isinstance(e, ExcelError):
307
+ return str(e)
308
+ else:
309
+ return "#VALUE!"
310
+ else:
311
+ return f"#NAME?"
312
+
313
+ def _precedence(self, token: Token) -> int:
314
+ """Get operator precedence."""
315
+ precedences = {
316
+ '^': 4,
317
+ '*': 3, '/': 3,
318
+ '+': 2, '-': 2,
319
+ '&': 2,
320
+ '=': 1, '<': 1, '>': 1, '<=': 1, '>=': 1, '<>': 1,
321
+ }
322
+ return precedences.get(token.value, 0)
323
+
324
+ def _apply_operator(self, op_token: Token, left: Any, right: Any) -> Any:
325
+ """Apply an operator to two operands."""
326
+ op = op_token.value
327
+
328
+ try:
329
+ if op == '+':
330
+ return float(left) + float(right)
331
+ elif op == '-':
332
+ return float(left) - float(right)
333
+ elif op == '*':
334
+ return float(left) * float(right)
335
+ elif op == '/':
336
+ if float(right) == 0:
337
+ raise DivisionByZeroError()
338
+ return float(left) / float(right)
339
+ elif op == '^':
340
+ return float(left) ** float(right)
341
+ elif op == '&':
342
+ return str(left) + str(right)
343
+ elif op == '=':
344
+ return left == right
345
+ elif op == '<':
346
+ return float(left) < float(right)
347
+ elif op == '>':
348
+ return float(left) > float(right)
349
+ elif op == '<=':
350
+ return float(left) <= float(right)
351
+ elif op == '>=':
352
+ return float(left) >= float(right)
353
+ elif op == '<>':
354
+ return left != right
355
+ else:
356
+ return 0
357
+ except (ValueError, TypeError):
358
+ raise ValueErrorExcel()
359
+ except ZeroDivisionError:
360
+ raise DivisionByZeroError()