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.
- aspose/__init__.py +14 -0
- aspose/cells/__init__.py +31 -0
- aspose/cells/cell.py +350 -0
- aspose/cells/constants.py +44 -0
- aspose/cells/converters/__init__.py +13 -0
- aspose/cells/converters/csv_converter.py +55 -0
- aspose/cells/converters/json_converter.py +46 -0
- aspose/cells/converters/markdown_converter.py +453 -0
- aspose/cells/drawing/__init__.py +17 -0
- aspose/cells/drawing/anchor.py +172 -0
- aspose/cells/drawing/collection.py +233 -0
- aspose/cells/drawing/image.py +338 -0
- aspose/cells/formats.py +80 -0
- aspose/cells/formula/__init__.py +10 -0
- aspose/cells/formula/evaluator.py +360 -0
- aspose/cells/formula/functions.py +433 -0
- aspose/cells/formula/tokenizer.py +340 -0
- aspose/cells/io/__init__.py +27 -0
- aspose/cells/io/csv/__init__.py +8 -0
- aspose/cells/io/csv/reader.py +88 -0
- aspose/cells/io/csv/writer.py +98 -0
- aspose/cells/io/factory.py +138 -0
- aspose/cells/io/interfaces.py +48 -0
- aspose/cells/io/json/__init__.py +8 -0
- aspose/cells/io/json/reader.py +126 -0
- aspose/cells/io/json/writer.py +119 -0
- aspose/cells/io/md/__init__.py +8 -0
- aspose/cells/io/md/reader.py +161 -0
- aspose/cells/io/md/writer.py +334 -0
- aspose/cells/io/models.py +64 -0
- aspose/cells/io/xlsx/__init__.py +9 -0
- aspose/cells/io/xlsx/constants.py +312 -0
- aspose/cells/io/xlsx/image_writer.py +311 -0
- aspose/cells/io/xlsx/reader.py +284 -0
- aspose/cells/io/xlsx/writer.py +931 -0
- aspose/cells/plugins/__init__.py +6 -0
- aspose/cells/plugins/docling_backend/__init__.py +7 -0
- aspose/cells/plugins/docling_backend/backend.py +535 -0
- aspose/cells/plugins/markitdown_plugin/__init__.py +15 -0
- aspose/cells/plugins/markitdown_plugin/plugin.py +128 -0
- aspose/cells/range.py +210 -0
- aspose/cells/style.py +287 -0
- aspose/cells/utils/__init__.py +54 -0
- aspose/cells/utils/coordinates.py +68 -0
- aspose/cells/utils/exceptions.py +43 -0
- aspose/cells/utils/validation.py +102 -0
- aspose/cells/workbook.py +352 -0
- aspose/cells/worksheet.py +670 -0
- aspose_cells_foss-25.12.1.dist-info/METADATA +189 -0
- aspose_cells_foss-25.12.1.dist-info/RECORD +53 -0
- aspose_cells_foss-25.12.1.dist-info/WHEEL +5 -0
- aspose_cells_foss-25.12.1.dist-info/entry_points.txt +2 -0
- aspose_cells_foss-25.12.1.dist-info/top_level.txt +1 -0
aspose/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Aspose.Cells.Python - Modern Excel processing library
|
|
3
|
+
|
|
4
|
+
Open source Excel processing library from Aspose.org providing Pythonic API
|
|
5
|
+
for Excel file manipulation with comprehensive data structures, conversion
|
|
6
|
+
capabilities, and extensible plugin ecosystem.
|
|
7
|
+
|
|
8
|
+
Part of the Aspose.org open source ecosystem.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from .cells import Workbook, FileFormat
|
|
12
|
+
|
|
13
|
+
__version__ = "1.0.0"
|
|
14
|
+
__all__ = ["Workbook", "FileFormat"]
|
aspose/cells/__init__.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core Excel processing module providing workbook, worksheet, and cell management.
|
|
3
|
+
|
|
4
|
+
Part of Aspose.Cells.Python - an open source Excel processing library from Aspose.org.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .workbook import Workbook
|
|
8
|
+
from .worksheet import Worksheet
|
|
9
|
+
from .cell import Cell
|
|
10
|
+
from .range import Range
|
|
11
|
+
from .formats import FileFormat, ConversionOptions, CellValue
|
|
12
|
+
from .style import Style, Font, Fill
|
|
13
|
+
from .drawing import Image, ImageFormat, Anchor, AnchorType, ImageCollection
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"Workbook",
|
|
17
|
+
"Worksheet",
|
|
18
|
+
"Cell",
|
|
19
|
+
"Range",
|
|
20
|
+
"FileFormat",
|
|
21
|
+
"ConversionOptions",
|
|
22
|
+
"CellValue",
|
|
23
|
+
"Style",
|
|
24
|
+
"Font",
|
|
25
|
+
"Fill",
|
|
26
|
+
"Image",
|
|
27
|
+
"ImageFormat",
|
|
28
|
+
"Anchor",
|
|
29
|
+
"AnchorType",
|
|
30
|
+
"ImageCollection"
|
|
31
|
+
]
|
aspose/cells/cell.py
ADDED
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Cell implementation with value management and styling capabilities.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional, TYPE_CHECKING
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
|
|
8
|
+
from .formats import CellValue
|
|
9
|
+
from .style import Style, Font, Fill, Border, Alignment
|
|
10
|
+
from .utils import (
|
|
11
|
+
infer_data_type,
|
|
12
|
+
convert_value,
|
|
13
|
+
tuple_to_coordinate,
|
|
14
|
+
CellValueError
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from .worksheet import Worksheet
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Cell:
|
|
22
|
+
"""Individual Excel cell with value, type, and styling management."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, worksheet: 'Worksheet', row: int, column: int, value: CellValue = None):
|
|
25
|
+
# Validate input parameters
|
|
26
|
+
if not isinstance(row, int) or row < 1:
|
|
27
|
+
raise ValueError(f"Row must be a positive integer, got: {row}")
|
|
28
|
+
if not isinstance(column, int) or column < 1:
|
|
29
|
+
raise ValueError(f"Column must be a positive integer, got: {column}")
|
|
30
|
+
|
|
31
|
+
self._worksheet = worksheet
|
|
32
|
+
self._row = row
|
|
33
|
+
self._column = column
|
|
34
|
+
self._value = value
|
|
35
|
+
self._data_type: Optional[str] = None
|
|
36
|
+
self._style: Optional[Style] = None
|
|
37
|
+
self._number_format: str = "General"
|
|
38
|
+
self._hyperlink: Optional[str] = None
|
|
39
|
+
self._comment: Optional[str] = None
|
|
40
|
+
self._formula: Optional[str] = None # Store original formula
|
|
41
|
+
self._calculated_value: Optional[CellValue] = None # Store calculated result
|
|
42
|
+
|
|
43
|
+
if value is not None:
|
|
44
|
+
self.value = value
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def row(self) -> int:
|
|
48
|
+
"""Row number (1-based)."""
|
|
49
|
+
return self._row
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def column(self) -> int:
|
|
53
|
+
"""Column number (1-based)."""
|
|
54
|
+
return self._column
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def coordinate(self) -> str:
|
|
58
|
+
"""Excel coordinate (e.g., 'A1')."""
|
|
59
|
+
return tuple_to_coordinate(self._row, self._column)
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def worksheet(self) -> 'Worksheet':
|
|
63
|
+
"""Parent worksheet."""
|
|
64
|
+
return self._worksheet
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def value(self) -> CellValue:
|
|
68
|
+
"""Cell value."""
|
|
69
|
+
return self._value
|
|
70
|
+
|
|
71
|
+
@value.setter
|
|
72
|
+
def value(self, val: CellValue):
|
|
73
|
+
"""Set cell value with automatic type inference."""
|
|
74
|
+
self._value = val
|
|
75
|
+
self._data_type = infer_data_type(val)
|
|
76
|
+
|
|
77
|
+
# Update worksheet bounds
|
|
78
|
+
if hasattr(self._worksheet, '_update_bounds'):
|
|
79
|
+
self._worksheet._update_bounds(self._row, self._column)
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def data_type(self) -> Optional[str]:
|
|
83
|
+
"""Inferred data type."""
|
|
84
|
+
return self._data_type
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def number_format(self) -> str:
|
|
88
|
+
"""Number format string."""
|
|
89
|
+
return self._number_format
|
|
90
|
+
|
|
91
|
+
@number_format.setter
|
|
92
|
+
def number_format(self, value: str):
|
|
93
|
+
"""Set number format."""
|
|
94
|
+
self._number_format = value
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def font(self) -> Font:
|
|
98
|
+
"""Font styling (creates style if needed)."""
|
|
99
|
+
if self._style is None:
|
|
100
|
+
self._style = Style()
|
|
101
|
+
return self._style.font
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def fill(self) -> Fill:
|
|
105
|
+
"""Fill styling (creates style if needed)."""
|
|
106
|
+
if self._style is None:
|
|
107
|
+
self._style = Style()
|
|
108
|
+
return self._style.fill
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def border(self) -> Border:
|
|
112
|
+
"""Border styling (creates style if needed)."""
|
|
113
|
+
if self._style is None:
|
|
114
|
+
self._style = Style()
|
|
115
|
+
return self._style.border
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def alignment(self) -> Alignment:
|
|
119
|
+
"""Alignment styling (creates style if needed)."""
|
|
120
|
+
if self._style is None:
|
|
121
|
+
self._style = Style()
|
|
122
|
+
return self._style.alignment
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def style(self) -> Style:
|
|
126
|
+
"""Complete style object (creates if needed)."""
|
|
127
|
+
if self._style is None:
|
|
128
|
+
self._style = Style()
|
|
129
|
+
return self._style
|
|
130
|
+
|
|
131
|
+
@style.setter
|
|
132
|
+
def style(self, value: Style):
|
|
133
|
+
"""Set complete style."""
|
|
134
|
+
self._style = value
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def hyperlink(self) -> Optional[str]:
|
|
138
|
+
"""Hyperlink URL."""
|
|
139
|
+
return self._hyperlink
|
|
140
|
+
|
|
141
|
+
@hyperlink.setter
|
|
142
|
+
def hyperlink(self, value: Optional[str]):
|
|
143
|
+
"""Set hyperlink URL."""
|
|
144
|
+
self._hyperlink = value
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def comment(self) -> Optional[str]:
|
|
148
|
+
"""Cell comment text."""
|
|
149
|
+
return self._comment
|
|
150
|
+
|
|
151
|
+
@comment.setter
|
|
152
|
+
def comment(self, value: Optional[str]):
|
|
153
|
+
"""Set cell comment."""
|
|
154
|
+
self._comment = value
|
|
155
|
+
|
|
156
|
+
def as_str(self, default: str = "") -> str:
|
|
157
|
+
"""Convert value to string."""
|
|
158
|
+
return convert_value(self._value, 'string', default)
|
|
159
|
+
|
|
160
|
+
def as_int(self, default: int = 0) -> int:
|
|
161
|
+
"""Convert value to integer."""
|
|
162
|
+
return convert_value(self._value, 'int', default)
|
|
163
|
+
|
|
164
|
+
def as_float(self, default: float = 0.0) -> float:
|
|
165
|
+
"""Convert value to float."""
|
|
166
|
+
return convert_value(self._value, 'float', default)
|
|
167
|
+
|
|
168
|
+
def as_bool(self, default: bool = False) -> bool:
|
|
169
|
+
"""Convert value to boolean."""
|
|
170
|
+
return convert_value(self._value, 'bool', default)
|
|
171
|
+
|
|
172
|
+
def is_numeric(self) -> bool:
|
|
173
|
+
"""Check if cell contains numeric value."""
|
|
174
|
+
return self._data_type == 'number'
|
|
175
|
+
|
|
176
|
+
def is_date(self) -> bool:
|
|
177
|
+
"""Check if cell contains date value."""
|
|
178
|
+
return self._data_type == 'date' or isinstance(self._value, datetime)
|
|
179
|
+
|
|
180
|
+
def is_formula(self) -> bool:
|
|
181
|
+
"""Check if cell contains formula."""
|
|
182
|
+
return self._data_type == 'formula'
|
|
183
|
+
|
|
184
|
+
def is_empty(self) -> bool:
|
|
185
|
+
"""Check if cell is empty."""
|
|
186
|
+
return self._value is None or self._data_type == 'empty'
|
|
187
|
+
|
|
188
|
+
def clear(self):
|
|
189
|
+
"""Clear cell value and formatting."""
|
|
190
|
+
self._value = None
|
|
191
|
+
self._data_type = 'empty'
|
|
192
|
+
self._style = None
|
|
193
|
+
self._number_format = "General"
|
|
194
|
+
self._hyperlink = None
|
|
195
|
+
self._comment = None
|
|
196
|
+
self._formula = None
|
|
197
|
+
self._calculated_value = None
|
|
198
|
+
|
|
199
|
+
def set_formula(self, formula: str, calculated_value: CellValue = None):
|
|
200
|
+
"""Set cell formula (handles = prefix automatically)."""
|
|
201
|
+
if not formula.startswith('='):
|
|
202
|
+
formula = '=' + formula
|
|
203
|
+
self._formula = formula
|
|
204
|
+
self.value = formula
|
|
205
|
+
self._data_type = 'formula'
|
|
206
|
+
|
|
207
|
+
# Always ensure calculated value is set
|
|
208
|
+
if calculated_value is not None:
|
|
209
|
+
self._calculated_value = calculated_value
|
|
210
|
+
else:
|
|
211
|
+
# Always recalculate when setting a new formula
|
|
212
|
+
self._calculated_value = self._get_basic_formula_result(formula)
|
|
213
|
+
|
|
214
|
+
@property
|
|
215
|
+
def formula(self) -> Optional[str]:
|
|
216
|
+
"""Get the original formula if this is a formula cell."""
|
|
217
|
+
return self._formula
|
|
218
|
+
|
|
219
|
+
@property
|
|
220
|
+
def calculated_value(self) -> CellValue:
|
|
221
|
+
"""Get the calculated result of a formula, or the cell value if not a formula."""
|
|
222
|
+
if self.is_formula() and self._calculated_value is not None:
|
|
223
|
+
return self._calculated_value
|
|
224
|
+
return self._value
|
|
225
|
+
|
|
226
|
+
@property
|
|
227
|
+
def display_value(self) -> str:
|
|
228
|
+
"""Get the display value (what should be shown to users)."""
|
|
229
|
+
if self.is_formula():
|
|
230
|
+
# For formulas, prefer calculated value over raw formula
|
|
231
|
+
if self._calculated_value is not None:
|
|
232
|
+
return str(self._calculated_value)
|
|
233
|
+
else:
|
|
234
|
+
return str(self._value) # Fallback to formula text
|
|
235
|
+
return str(self._value) if self._value is not None else ""
|
|
236
|
+
|
|
237
|
+
def get_value(self, mode: str = 'display') -> CellValue:
|
|
238
|
+
"""
|
|
239
|
+
Get cell value in different modes.
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
mode: 'display' (calculated/display value), 'formula' (raw formula), 'raw' (raw value)
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
Cell value based on the requested mode
|
|
246
|
+
"""
|
|
247
|
+
if mode == 'formula' and self.is_formula():
|
|
248
|
+
return self._formula or self._value
|
|
249
|
+
elif mode == 'display':
|
|
250
|
+
return self.calculated_value
|
|
251
|
+
elif mode == 'raw':
|
|
252
|
+
return self._value
|
|
253
|
+
else:
|
|
254
|
+
return self.calculated_value # Default to display mode
|
|
255
|
+
|
|
256
|
+
def has_hyperlink(self) -> bool:
|
|
257
|
+
"""Check if cell has a hyperlink."""
|
|
258
|
+
return self._hyperlink is not None and self._hyperlink.strip() != ""
|
|
259
|
+
|
|
260
|
+
def get_markdown_link(self, text: Optional[str] = None) -> str:
|
|
261
|
+
"""Get markdown formatted link if cell has hyperlink."""
|
|
262
|
+
if not self.has_hyperlink():
|
|
263
|
+
return text or self.display_value
|
|
264
|
+
|
|
265
|
+
link_text = text or self.display_value
|
|
266
|
+
if not link_text:
|
|
267
|
+
link_text = self._hyperlink
|
|
268
|
+
|
|
269
|
+
return f"[{link_text}]({self._hyperlink})"
|
|
270
|
+
|
|
271
|
+
def set_hyperlink(self, url: str, display_text: Optional[str] = None):
|
|
272
|
+
"""Set hyperlink with optional display text."""
|
|
273
|
+
self._hyperlink = url
|
|
274
|
+
if display_text is not None:
|
|
275
|
+
self.value = display_text
|
|
276
|
+
|
|
277
|
+
def copy_from(self, other: 'Cell'):
|
|
278
|
+
"""Copy value and style from another cell."""
|
|
279
|
+
self.value = other.value
|
|
280
|
+
if other._style:
|
|
281
|
+
self._style = other._style.copy()
|
|
282
|
+
self._number_format = other._number_format
|
|
283
|
+
self._hyperlink = other._hyperlink
|
|
284
|
+
self._comment = other._comment
|
|
285
|
+
self._formula = other._formula
|
|
286
|
+
self._calculated_value = other._calculated_value
|
|
287
|
+
|
|
288
|
+
def __str__(self) -> str:
|
|
289
|
+
"""String representation."""
|
|
290
|
+
return f"Cell({self.coordinate}={self._value})"
|
|
291
|
+
|
|
292
|
+
def _get_basic_formula_result(self, formula: str) -> CellValue:
|
|
293
|
+
"""Get calculated result using the formula engine."""
|
|
294
|
+
try:
|
|
295
|
+
from .formula import FormulaEvaluator
|
|
296
|
+
evaluator = FormulaEvaluator(self._worksheet)
|
|
297
|
+
return evaluator.evaluate(formula, self.coordinate)
|
|
298
|
+
except Exception:
|
|
299
|
+
# Fallback to simple evaluation
|
|
300
|
+
return self._simple_formula_fallback(formula)
|
|
301
|
+
|
|
302
|
+
def _simple_formula_fallback(self, formula: str) -> CellValue:
|
|
303
|
+
"""Simple fallback for basic formulas when engine fails."""
|
|
304
|
+
formula_upper = formula.upper().strip()
|
|
305
|
+
|
|
306
|
+
# Remove = prefix
|
|
307
|
+
if formula_upper.startswith('='):
|
|
308
|
+
formula_upper = formula_upper[1:]
|
|
309
|
+
|
|
310
|
+
# Handle very basic cases
|
|
311
|
+
if formula_upper.startswith(('SUM', 'COUNT', 'AVERAGE', 'MAX', 'MIN')):
|
|
312
|
+
return 0
|
|
313
|
+
elif formula_upper.startswith(('NOW', 'TODAY')):
|
|
314
|
+
return "2024-01-01"
|
|
315
|
+
elif formula_upper.startswith('TRUE'):
|
|
316
|
+
return True
|
|
317
|
+
elif formula_upper.startswith('FALSE'):
|
|
318
|
+
return False
|
|
319
|
+
elif all(c in '0123456789+-*/.() ' for c in formula_upper):
|
|
320
|
+
try:
|
|
321
|
+
# Use safe expression evaluation instead of eval
|
|
322
|
+
import ast
|
|
323
|
+
node = ast.parse(formula_upper, mode='eval')
|
|
324
|
+
if self._is_safe_expression(node):
|
|
325
|
+
return eval(compile(node, '<string>', 'eval'))
|
|
326
|
+
else:
|
|
327
|
+
return 0
|
|
328
|
+
except (ValueError, SyntaxError, TypeError):
|
|
329
|
+
return 0
|
|
330
|
+
else:
|
|
331
|
+
return 0
|
|
332
|
+
|
|
333
|
+
def _is_safe_expression(self, node) -> bool:
|
|
334
|
+
"""Check if AST node contains only safe mathematical operations."""
|
|
335
|
+
import ast
|
|
336
|
+
|
|
337
|
+
allowed_nodes = (
|
|
338
|
+
ast.Expression, ast.BinOp, ast.UnaryOp, ast.Constant, ast.Num,
|
|
339
|
+
ast.Add, ast.Sub, ast.Mult, ast.Div, ast.Mod, ast.Pow,
|
|
340
|
+
ast.USub, ast.UAdd
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
for child in ast.walk(node):
|
|
344
|
+
if not isinstance(child, allowed_nodes):
|
|
345
|
+
return False
|
|
346
|
+
return True
|
|
347
|
+
|
|
348
|
+
def __repr__(self) -> str:
|
|
349
|
+
"""Debug representation."""
|
|
350
|
+
return f"Cell({self.coordinate}, row={self._row}, col={self._column}, value={self._value!r}, type={self._data_type})"
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Constants for Excel file format specifications and limits.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
# Excel worksheet limits
|
|
6
|
+
MAX_SHEET_NAME_LENGTH = 31
|
|
7
|
+
MAX_ROWS = 1048576 # Excel 2007+ limit
|
|
8
|
+
MAX_COLUMNS = 16384 # Excel 2007+ limit (XFD column)
|
|
9
|
+
MAX_CELL_CONTENT_LENGTH = 32767 # Maximum characters in a cell
|
|
10
|
+
|
|
11
|
+
# Column width limits
|
|
12
|
+
MIN_COLUMN_WIDTH = 0.0
|
|
13
|
+
MAX_COLUMN_WIDTH = 255.0
|
|
14
|
+
DEFAULT_COLUMN_WIDTH = 8.43
|
|
15
|
+
|
|
16
|
+
# Row height limits
|
|
17
|
+
MIN_ROW_HEIGHT = 0.0
|
|
18
|
+
MAX_ROW_HEIGHT = 409.5
|
|
19
|
+
DEFAULT_ROW_HEIGHT = 15.0
|
|
20
|
+
|
|
21
|
+
# Style limits
|
|
22
|
+
MAX_FONT_SIZE = 409
|
|
23
|
+
MIN_FONT_SIZE = 1
|
|
24
|
+
DEFAULT_FONT_SIZE = 11
|
|
25
|
+
|
|
26
|
+
# File format extensions
|
|
27
|
+
EXCEL_EXTENSIONS = {'.xlsx', '.xls', '.xlsm', '.xlsb'}
|
|
28
|
+
CSV_EXTENSIONS = {'.csv', '.tsv'}
|
|
29
|
+
TEXT_EXTENSIONS = {'.txt', '.tab'}
|
|
30
|
+
|
|
31
|
+
# Invalid characters for sheet names
|
|
32
|
+
INVALID_SHEET_NAME_CHARS = ['\\', '/', '?', '*', '[', ']', ':']
|
|
33
|
+
|
|
34
|
+
# Excel date constants
|
|
35
|
+
EXCEL_EPOCH_DATE = "1900-01-01"
|
|
36
|
+
EXCEL_EPOCH_DATETIME = "1900-01-01T00:00:00"
|
|
37
|
+
|
|
38
|
+
# Cell reference patterns
|
|
39
|
+
CELL_REF_PATTERN = r'^[A-Z]{1,3}[1-9]\d*$'
|
|
40
|
+
RANGE_REF_PATTERN = r'^[A-Z]{1,3}[1-9]\d*:[A-Z]{1,3}[1-9]\d*$'
|
|
41
|
+
|
|
42
|
+
# Default values
|
|
43
|
+
DEFAULT_SHEET_NAME = "Sheet"
|
|
44
|
+
DEFAULT_WORKBOOK_NAME = "Workbook"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Data conversion modules for exporting Excel data to various formats.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .json_converter import JsonConverter
|
|
6
|
+
from .csv_converter import CsvConverter
|
|
7
|
+
from .markdown_converter import MarkdownConverter
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"JsonConverter",
|
|
11
|
+
"CsvConverter",
|
|
12
|
+
"MarkdownConverter"
|
|
13
|
+
]
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CSV converter for exporting Excel data to CSV format.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import csv
|
|
6
|
+
import io
|
|
7
|
+
from typing import Optional, TYPE_CHECKING
|
|
8
|
+
from ..io.csv import CsvWriter
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from ..workbook import Workbook
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CsvConverter:
|
|
15
|
+
"""Convert Excel workbook data to CSV format."""
|
|
16
|
+
|
|
17
|
+
def __init__(self):
|
|
18
|
+
self._writer = CsvWriter()
|
|
19
|
+
|
|
20
|
+
def convert_workbook(self, workbook: 'Workbook', **kwargs) -> str:
|
|
21
|
+
"""Convert active worksheet to CSV string."""
|
|
22
|
+
sheet_name = kwargs.get('sheet_name')
|
|
23
|
+
delimiter = kwargs.get('delimiter', ',')
|
|
24
|
+
quotechar = kwargs.get('quotechar', '"')
|
|
25
|
+
|
|
26
|
+
# Get target worksheet
|
|
27
|
+
if sheet_name and sheet_name in workbook._worksheets:
|
|
28
|
+
worksheet = workbook._worksheets[sheet_name]
|
|
29
|
+
else:
|
|
30
|
+
worksheet = workbook.active
|
|
31
|
+
|
|
32
|
+
if not worksheet or not worksheet._cells:
|
|
33
|
+
return ""
|
|
34
|
+
|
|
35
|
+
# Convert worksheet to data
|
|
36
|
+
data = self._writer._worksheet_to_data(worksheet)
|
|
37
|
+
|
|
38
|
+
if not data:
|
|
39
|
+
return ""
|
|
40
|
+
|
|
41
|
+
# Create CSV in memory
|
|
42
|
+
output = io.StringIO()
|
|
43
|
+
writer = csv.writer(output, delimiter=delimiter, quotechar=quotechar,
|
|
44
|
+
quoting=csv.QUOTE_MINIMAL)
|
|
45
|
+
|
|
46
|
+
# Write data rows
|
|
47
|
+
for row_data in data:
|
|
48
|
+
formatted_row = []
|
|
49
|
+
for cell in row_data:
|
|
50
|
+
formatted_row.append(self._writer._format_cell_value(cell))
|
|
51
|
+
writer.writerow(formatted_row)
|
|
52
|
+
|
|
53
|
+
csv_content = output.getvalue()
|
|
54
|
+
output.close()
|
|
55
|
+
return csv_content
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""
|
|
2
|
+
JSON converter for exporting Excel data to JSON format.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import Dict, List, Optional, Union, TYPE_CHECKING
|
|
7
|
+
from ..io.json import JsonWriter
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from ..workbook import Workbook
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class JsonConverter:
|
|
14
|
+
"""Convert Excel workbook data to JSON format."""
|
|
15
|
+
|
|
16
|
+
def __init__(self):
|
|
17
|
+
self._writer = JsonWriter()
|
|
18
|
+
|
|
19
|
+
def convert_workbook(self, workbook: 'Workbook', **kwargs) -> str:
|
|
20
|
+
"""Convert entire workbook to JSON string."""
|
|
21
|
+
pretty_print = kwargs.get('pretty_print', False)
|
|
22
|
+
include_empty_cells = kwargs.get('include_empty_cells', False)
|
|
23
|
+
all_sheets = kwargs.get('all_sheets', False)
|
|
24
|
+
sheet_name = kwargs.get('sheet_name')
|
|
25
|
+
|
|
26
|
+
if sheet_name:
|
|
27
|
+
# Export specific sheet
|
|
28
|
+
if sheet_name in workbook._worksheets:
|
|
29
|
+
worksheet = workbook._worksheets[sheet_name]
|
|
30
|
+
result = self._writer._convert_worksheet(worksheet, include_empty_cells)
|
|
31
|
+
else:
|
|
32
|
+
result = []
|
|
33
|
+
elif all_sheets:
|
|
34
|
+
# Export all sheets with sheet names as keys
|
|
35
|
+
result = {}
|
|
36
|
+
for name, worksheet in workbook._worksheets.items():
|
|
37
|
+
sheet_data = self._writer._convert_worksheet(worksheet, include_empty_cells)
|
|
38
|
+
result[name] = sheet_data
|
|
39
|
+
else:
|
|
40
|
+
# Export only active sheet as simple list
|
|
41
|
+
result = self._writer._convert_worksheet(workbook.active, include_empty_cells)
|
|
42
|
+
|
|
43
|
+
if pretty_print:
|
|
44
|
+
return json.dumps(result, indent=2, ensure_ascii=False)
|
|
45
|
+
else:
|
|
46
|
+
return json.dumps(result, ensure_ascii=False)
|