gnumeric 0.1.0__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.
gnumeric/utils.py ADDED
@@ -0,0 +1,131 @@
1
+ """
2
+ Gnumeric-py: Reading and writing gnumeric files with python
3
+ Copyright (C) 2017 Michael Lipschultz
4
+
5
+ This program is free software: you can redistribute it and/or modify
6
+ it under the terms of the GNU General Public License as published by
7
+ the Free Software Foundation, either version 3 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU General Public License for more details.
14
+
15
+ You should have received a copy of the GNU General Public License
16
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+ """
18
+
19
+ from typing import NamedTuple, Optional, Union
20
+
21
+
22
+ class RowColReference(NamedTuple):
23
+ row: int
24
+ col: int
25
+
26
+
27
+ def column_to_spreadsheet(col_int: int, abs_ref: bool = False) -> str:
28
+ """
29
+ Convert 0-indexed column number into standard spreadsheet notation. For example: `30` -> `'AE'`.
30
+
31
+ Raises `IndexError` when `col_int` is negative.
32
+ """
33
+ if col_int < 0:
34
+ raise IndexError('The column index must be positive: ' + str(col_int))
35
+
36
+ column = chr((col_int % 26) + ord('A'))
37
+ while col_int >= 26:
38
+ col_int = col_int // 26 - 1
39
+ column = chr((col_int % 26) + ord('A')) + column
40
+
41
+ abs_col = '$' if abs_ref else ''
42
+ return '%s%s' % (abs_col, column)
43
+
44
+
45
+ def column_from_spreadsheet(col_letter: str) -> int:
46
+ """
47
+ Convert standard spreadsheet notation for a column into a 0-indexed column number. For example: `'AE'` -> `30`.
48
+
49
+ Capitalization of the letters is ignored, so `'ae'` and `'Ae'` also convert to `30`.
50
+
51
+ Raises `IndexError` is any character in `col_letter` is not a letter.
52
+ """
53
+ col_letter = col_letter.replace('$', '')
54
+ index = 0
55
+ for letter in col_letter:
56
+ if not letter.isalpha():
57
+ raise IndexError(f'Illegal character in column name: {letter}')
58
+ index = index * 26 + (ord(letter) - ord('A') + 1)
59
+ return index - 1
60
+
61
+
62
+ def row_to_spreadsheet(row: int, abs_ref: bool = False) -> str:
63
+ """
64
+ Convert a 0-indexed row index into the standard spreadsheet row (which is 1-indexed). If `abs_ref` is True, then
65
+ the returned value will be an absolute reference to the row.
66
+
67
+ Raises `IndexError` if row is less than 1.
68
+ """
69
+ if row < 0:
70
+ raise IndexError('row must be greater than or equal to 0')
71
+
72
+ abs_ref = '$' if abs_ref else ''
73
+ return abs_ref + str(row + 1)
74
+
75
+
76
+ def row_from_spreadsheet(row: str) -> int:
77
+ """
78
+ Convert the standard spreadsheet row numbers (1-indexed) to 0-indexed index.
79
+
80
+ Raises `IndexError` if row is less than 1.
81
+ """
82
+ row = int(row.replace('$', ''))
83
+ if row < 1:
84
+ raise IndexError('row must be greater than or equal to 1')
85
+
86
+ return row - 1
87
+
88
+
89
+ def coordinate_to_spreadsheet(
90
+ coord: Union[int, RowColReference],
91
+ col: Optional[int] = None,
92
+ abs_ref_row: bool = False,
93
+ abs_ref_col: bool = False,
94
+ ) -> str:
95
+ """
96
+ Convert a coordinate into spreadsheet notation. If `col` is `None`, then `coord` is assumed to be a (row, column)
97
+ tuple. If `col` is not `None`, then `coord` is the row and col is the column.
98
+
99
+ If `abs_ref_row` is True, then the resulting coordinate will have the row be an absolute reference. Then same is
100
+ true for `abs_ref_col` about the column.
101
+
102
+ Example (6, 2) -> 'C7'
103
+ """
104
+ if col is None:
105
+ coord, col = coord
106
+
107
+ return column_to_spreadsheet(col, abs_ref_col) + row_to_spreadsheet(
108
+ coord, abs_ref_row
109
+ )
110
+
111
+
112
+ def coordinate_from_spreadsheet(coord: str) -> RowColReference:
113
+ """
114
+ Convert a coordinate from spreadsheet notation into a (row, column) tuple.
115
+
116
+ Example `'C$7'` -> `(6, 2)`
117
+ """
118
+ coord = coord.replace('$', '')
119
+
120
+ first_row_position = None
121
+ i = 0
122
+ while first_row_position is None and i < len(coord):
123
+ if not coord[i].isalpha():
124
+ first_row_position = i
125
+
126
+ i += 1
127
+
128
+ return RowColReference(
129
+ row_from_spreadsheet(coord[first_row_position:]),
130
+ column_from_spreadsheet(coord[:first_row_position]),
131
+ )
gnumeric/workbook.py ADDED
@@ -0,0 +1,423 @@
1
+ """
2
+ Gnumeric-py: Reading and writing gnumeric files with python
3
+ Copyright (C) 2017 Michael Lipschultz
4
+
5
+ This program is free software: you can redistribute it and/or modify
6
+ it under the terms of the GNU General Public License as published by
7
+ the Free Software Foundation, either version 3 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU General Public License for more details.
14
+
15
+ You should have received a copy of the GNU General Public License
16
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+ """
18
+
19
+ import gzip
20
+ from datetime import datetime
21
+ from typing import List, Optional, Self, Union
22
+ from pathlib import Path
23
+
24
+ import dateutil.parser
25
+ from lxml import etree
26
+
27
+ from gnumeric import sheet
28
+ from gnumeric.exceptions import DuplicateTitleException, WrongWorkbookException
29
+ from gnumeric.sheet import Sheet
30
+
31
+ EMPTY_WORKBOOK = b"""<?xml version="1.0" encoding="UTF-8"?>
32
+ <gnm:Workbook xmlns:gnm="http://www.gnumeric.org/v10.dtd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.gnumeric.org/v9.xsd">
33
+ <gnm:Version Epoch="1" Major="12" Minor="28" Full="1.12.28"/>
34
+ <gnm:Attributes>
35
+ <gnm:Attribute>
36
+ <gnm:name>WorkbookView::show_horizontal_scrollbar</gnm:name>
37
+ <gnm:value>TRUE</gnm:value>
38
+ </gnm:Attribute>
39
+ <gnm:Attribute>
40
+ <gnm:name>WorkbookView::show_vertical_scrollbar</gnm:name>
41
+ <gnm:value>TRUE</gnm:value>
42
+ </gnm:Attribute>
43
+ <gnm:Attribute>
44
+ <gnm:name>WorkbookView::show_notebook_tabs</gnm:name>
45
+ <gnm:value>TRUE</gnm:value>
46
+ </gnm:Attribute>
47
+ <gnm:Attribute>
48
+ <gnm:name>WorkbookView::do_auto_completion</gnm:name>
49
+ <gnm:value>TRUE</gnm:value>
50
+ </gnm:Attribute>
51
+ <gnm:Attribute>
52
+ <gnm:name>WorkbookView::is_protected</gnm:name>
53
+ <gnm:value>FALSE</gnm:value>
54
+ </gnm:Attribute>
55
+ </gnm:Attributes>
56
+ <office:document-meta xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:ooo="http://openoffice.org/2004/office" office:version="1.2">
57
+ <office:meta>
58
+ <meta:creation-date>2017-04-27T22:47:06Z</meta:creation-date>
59
+ </office:meta>
60
+ </office:document-meta>
61
+ <gnm:Calculation ManualRecalc="0" EnableIteration="1" MaxIterations="100" IterationTolerance="0.001" FloatRadix="2" FloatDigits="53"/>
62
+ <gnm:SheetNameIndex>
63
+ </gnm:SheetNameIndex>
64
+ <gnm:Geometry Width="1024" Height="383"/>
65
+ <gnm:Sheets>
66
+ </gnm:Sheets>
67
+ <gnm:UIData SelectedTab="0"/>
68
+ </gnm:Workbook>
69
+ """
70
+ ALL_NAMESPACES = {
71
+ 'gnm': 'http://www.gnumeric.org/v10.dtd',
72
+ 'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
73
+ 'office': 'urn:oasis:names:tc:opendocument:xmlns:office:1.0',
74
+ 'xlink': 'http://www.w3.org/1999/xlink',
75
+ 'dc': 'http://purl.org/dc/elements/1.1/',
76
+ 'meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
77
+ 'ooo': 'http://openoffice.org/2004/office',
78
+ }
79
+
80
+ NEW_SHEET_NAME = b"""<?xml version="1.0" encoding="UTF-8"?><gnm:ROOT xmlns:gnm="http://www.gnumeric.org/v10.dtd"><gnm:SheetName gnm:Cols="256" gnm:Rows="65536"></gnm:SheetName></gnm:ROOT>"""
81
+ NEW_SHEET = b"""<?xml version="1.0" encoding="UTF-8"?><gnm:ROOT xmlns:gnm="http://www.gnumeric.org/v10.dtd">
82
+ <gnm:Sheet DisplayFormulas="0" HideZero="0" HideGrid="0" HideColHeader="0" HideRowHeader="0" DisplayOutlines="1" OutlineSymbolsBelow="1" OutlineSymbolsRight="1" Visibility="GNM_SHEET_VISIBILITY_VISIBLE" GridColor="0:0:0">
83
+ <gnm:Name></gnm:Name>
84
+ <gnm:MaxCol>-1</gnm:MaxCol>
85
+ <gnm:MaxRow>-1</gnm:MaxRow>
86
+ <gnm:Zoom>1</gnm:Zoom>
87
+ <gnm:Names>
88
+ <gnm:Name>
89
+ <gnm:name>Print_Area</gnm:name>
90
+ <gnm:value>#REF!</gnm:value>
91
+ <gnm:position>A1</gnm:position>
92
+ </gnm:Name>
93
+ <gnm:Name>
94
+ <gnm:name>Sheet_Title</gnm:name>
95
+ <gnm:value>&quot;Sheet1&quot;</gnm:value>
96
+ <gnm:position>A1</gnm:position>
97
+ </gnm:Name>
98
+ </gnm:Names>
99
+ <gnm:PrintInformation>
100
+ <gnm:Margins>
101
+ <gnm:top Points="120" PrefUnit="mm"/>
102
+ <gnm:bottom Points="120" PrefUnit="mm"/>
103
+ <gnm:left Points="72" PrefUnit="mm"/>
104
+ <gnm:right Points="72" PrefUnit="mm"/>
105
+ <gnm:header Points="72" PrefUnit="mm"/>
106
+ <gnm:footer Points="72" PrefUnit="mm"/>
107
+ </gnm:Margins>
108
+ <gnm:Scale type="percentage" percentage="100"/>
109
+ <gnm:vcenter value="0"/>
110
+ <gnm:hcenter value="0"/>
111
+ <gnm:grid value="0"/>
112
+ <gnm:even_if_only_styles value="0"/>
113
+ <gnm:monochrome value="0"/>
114
+ <gnm:draft value="0"/>
115
+ <gnm:titles value="0"/>
116
+ <gnm:do_not_print value="0"/>
117
+ <gnm:print_range value="GNM_PRINT_ACTIVE_SHEET"/>
118
+ <gnm:order>d_then_r</gnm:order>
119
+ <gnm:orientation>portrait</gnm:orientation>
120
+ <gnm:Header Left="" Middle="&amp;[TAB]" Right=""/>
121
+ <gnm:Footer Left="" Middle="Page &amp;[PAGE]" Right=""/>
122
+ <gnm:paper>na_letter</gnm:paper>
123
+ <gnm:comments placement="GNM_PRINT_COMMENTS_IN_PLACE"/>
124
+ <gnm:errors PrintErrorsAs="GNM_PRINT_ERRORS_AS_DISPLAYED"/>
125
+ </gnm:PrintInformation>
126
+ <gnm:Styles>
127
+ <gnm:StyleRegion startCol="0" startRow="0" endCol="255" endRow="65535">
128
+ <gnm:Style HAlign="GNM_HALIGN_GENERAL" VAlign="GNM_VALIGN_BOTTOM" WrapText="0" ShrinkToFit="0" Rotation="0" Shade="0" Indent="0" Locked="1" Hidden="0" Fore="0:0:0" Back="FFFF:FFFF:FFFF" PatternColor="0:0:0" Format="General">
129
+ <gnm:Font Unit="10" Bold="0" Italic="0" Underline="0" StrikeThrough="0" Script="0">Sans</gnm:Font>
130
+ </gnm:Style>
131
+ </gnm:StyleRegion>
132
+ </gnm:Styles>
133
+ <gnm:Cols DefaultSizePts="48"/>
134
+ <gnm:Rows DefaultSizePts="12.75"/>
135
+ <gnm:Selections CursorCol="0" CursorRow="0">
136
+ <gnm:Selection startCol="0" startRow="0" endCol="0" endRow="0"/>
137
+ </gnm:Selections>
138
+ <gnm:Cells/>
139
+ <gnm:SheetLayout TopLeft="A1"/>
140
+ <gnm:Solver ModelType="0" ProblemType="0" MaxTime="60" MaxIter="1000" NonNeg="1" Discr="0" AutoScale="0" ProgramR="0" SensitivityR="0"/>
141
+ </gnm:Sheet>
142
+ </gnm:ROOT>
143
+ """
144
+
145
+
146
+ class Workbook:
147
+ def __init__(self, workbook_root_element=None):
148
+ self._ns = ALL_NAMESPACES
149
+ if workbook_root_element is None:
150
+ self.__root = etree.fromstring(EMPTY_WORKBOOK)
151
+ self.creation_date = datetime.now()
152
+ else:
153
+ self.__root = workbook_root_element
154
+
155
+ def __creation_date_element(self):
156
+ return self.__root.find(
157
+ 'office:document-meta/office:meta/meta:creation-date', self._ns
158
+ )
159
+
160
+ def __sheet_name_elements(self):
161
+ return self.__root.find('gnm:SheetNameIndex', self._ns)
162
+
163
+ def __sheet_elements(self):
164
+ return self.__root.find('gnm:Sheets', self._ns)
165
+
166
+ def __get_ui_data_element(self):
167
+ return self.__root.find('gnm:UIData', self._ns)
168
+
169
+ @property
170
+ def version(self) -> str:
171
+ """
172
+ The Gnumeric format version of the workbook
173
+ """
174
+ version = self.__root.find('gnm:Version', self._ns)
175
+ return version.get('Full')
176
+
177
+ def get_creation_date(self) -> datetime:
178
+ """
179
+ Date the workbook was created
180
+ """
181
+ creation_element = self.__creation_date_element()
182
+ return dateutil.parser.parse(creation_element.text)
183
+
184
+ def set_creation_date(self, creation_datetime: datetime) -> None:
185
+ """
186
+ :param creation_datetime: A datetime object representing when this workbook was created
187
+ """
188
+ creation_element = self.__creation_date_element()
189
+ creation_element.text = creation_datetime.isoformat()
190
+
191
+ creation_date = property(get_creation_date, set_creation_date)
192
+
193
+ def __len__(self) -> int:
194
+ """
195
+ The number of sheets in the workbook
196
+ """
197
+ return len(self.__sheet_elements())
198
+
199
+ def get_sheet_names(self) -> List[str]:
200
+ """
201
+ The list of sheet names, in the order they occur in the workbook.
202
+ """
203
+ return [s.text for s in self.__sheet_name_elements()]
204
+
205
+ @property
206
+ def sheetnames(self) -> List[str]:
207
+ """
208
+ The list of sheet names, in the order they occur in the workbook.
209
+ """
210
+ return self.get_sheet_names()
211
+
212
+ def create_sheet(self, title: str, *, index: int = -1) -> Sheet:
213
+ """
214
+ Create a new worksheet
215
+
216
+ :param title: Title, or name, of worksheet
217
+ :param index: Where to insert the new sheet within the list of sheets. Default is `-1` (to append).
218
+ :raises DuplicateTitleException: When a sheet with the same title already exists in the workbook
219
+ :return: The worksheet
220
+ """
221
+ if title in self.sheetnames:
222
+ raise DuplicateTitleException('A sheet titled "%s" already exists' % title)
223
+
224
+ sheet_name_element = etree.fromstring(NEW_SHEET_NAME).getchildren()[0]
225
+ sheet_element = etree.fromstring(NEW_SHEET).getchildren()[0]
226
+
227
+ if index < 0:
228
+ index = len(self) + index + 1
229
+ self.__sheet_name_elements().insert(index, sheet_name_element)
230
+ self.__sheet_elements().insert(index, sheet_element)
231
+
232
+ ws = Sheet(sheet_name_element, sheet_element, self)
233
+ ws.title = title
234
+ return ws
235
+
236
+ def get_active_sheet(self) -> Optional[Sheet]:
237
+ """
238
+ The sheet that is selected, or active, in the workbook.
239
+
240
+ :return: The active sheet, or `None` if there are no sheets.
241
+ """
242
+ if len(self) == 0:
243
+ return None
244
+ else:
245
+ return self.get_sheet_by_index(
246
+ int(self.__get_ui_data_element().get('SelectedTab'))
247
+ )
248
+
249
+ def set_active_sheet(self, sheet: Union[int, str, Sheet]) -> None:
250
+ """
251
+ Given a sheet, set it as the active sheet in the workbook.
252
+
253
+ :param sheet: An `int` (the sheet's index), a `str` (the name of the sheet), or a `Sheet` object
254
+ """
255
+ if isinstance(sheet, int):
256
+ self.__get_ui_data_element().set('SelectedTab', str(sheet))
257
+ elif isinstance(sheet, str):
258
+ self.set_active_sheet(self.sheetnames.index(sheet))
259
+ elif isinstance(sheet, Sheet):
260
+ self.set_active_sheet(self.get_index(sheet))
261
+
262
+ active = property(get_active_sheet, set_active_sheet)
263
+
264
+ def get_sheet_by_index(self, index: int) -> Sheet:
265
+ """
266
+ Get the sheet at the specified index.
267
+
268
+ Supports negative indexing, so `-1` will get the last sheet.
269
+
270
+ :raises IndexError: When index is out of bounds
271
+ """
272
+ return Sheet(
273
+ self.__sheet_name_elements()[index], self.__sheet_elements()[index], self
274
+ )
275
+
276
+ def get_sheet_by_name(self, name: str) -> Sheet:
277
+ """
278
+ Get the sheet with the specified title/name
279
+
280
+ :raises KeyError: When no worksheet with that name exists
281
+ """
282
+ names = self.sheetnames
283
+ try:
284
+ idx = names.index(name)
285
+ return self.get_sheet_by_index(idx)
286
+ except ValueError:
287
+ raise KeyError('No sheet named "%s" exists' % name)
288
+
289
+ def __getitem__(self, key: Union[str, int]) -> Sheet:
290
+ """
291
+ Get a worksheet by name or by index. If `key` is a string, then it is treated as a worksheet's name. If it is
292
+ an int, then it's treated as an index.
293
+
294
+ Raises a `TypeError` if key is not and `int` or a `str`. Can also raise the exceptions that
295
+ `get_sheet_by_index` and `get_sheet_by_name` raise.
296
+ """
297
+ if isinstance(key, str):
298
+ return self.get_sheet_by_name(key)
299
+ elif isinstance(key, int):
300
+ return self.get_sheet_by_index(key)
301
+ else:
302
+ raise TypeError('Unexpected type (%s) for key: %s' % (type(key), str(key)))
303
+
304
+ def get_index(self, ws: Sheet) -> int:
305
+ """
306
+ Given a worksheet, find its index in the workbook.
307
+ """
308
+ index = self.sheetnames.index(ws.title)
309
+ if ws != self.get_sheet_by_index(index):
310
+ raise WrongWorkbookException(
311
+ 'The worksheet does not belong to this workbook.'
312
+ )
313
+ return index
314
+
315
+ index = get_index
316
+
317
+ def remove_sheet_by_name(self, name: str) -> None:
318
+ """
319
+ Remove the worksheet named `name` from the workbook. See `get_sheet_by_name` and `remove_sheet` for exceptions
320
+ thrown.
321
+ """
322
+ self.remove_sheet(self.get_sheet_by_name(name))
323
+
324
+ def remove_sheet_by_index(self, index: int) -> None:
325
+ """
326
+ Remove the worksheet at `index` from the workbook. See `get_sheet_by_index` and `remove_sheet` for exceptions
327
+ thrown.
328
+ """
329
+ self.remove_sheet(self.get_sheet_by_index(index))
330
+
331
+ def remove_sheet(self, ws: Sheet) -> None:
332
+ """
333
+ Remove the worksheet from the workbook.
334
+
335
+ Raises `WrongWorkbookException` if worksheet is not part of this workbook.
336
+ """
337
+ self.get_index(ws)
338
+ ws.remove_from_workbook()
339
+
340
+ def remove(self, ws: Union[int, str, Sheet]) -> None:
341
+ """
342
+ Remove the specified worksheet
343
+
344
+ :param ws: Can be the worksheet to remove, the index of the worksheet, or the name of the worksheet.
345
+ """
346
+ if isinstance(ws, int):
347
+ self.remove_sheet_by_index(ws)
348
+ elif isinstance(ws, str):
349
+ self.remove_sheet_by_name(ws)
350
+ else:
351
+ self.remove_sheet(ws)
352
+
353
+ def __delitem__(self, key: Union[int, str, Sheet]) -> None:
354
+ """
355
+ Remove the specified worksheet
356
+
357
+ :param key: Can be the worksheet to remove, the index of the worksheet, or the name of the worksheet.
358
+ """
359
+ self.remove(key)
360
+
361
+ @property
362
+ def sheets(self) -> List[Sheet]:
363
+ """
364
+ Get a list of all sheets in the workbook.
365
+ """
366
+ return [self.get_sheet_by_index(i) for i in range(len(self))]
367
+
368
+ @property
369
+ def chartsheets(self) -> List[Sheet]:
370
+ """
371
+ Get list of only the chart sheets in the workbook.
372
+ """
373
+ return [s for s in self.sheets if s.type == sheet.SHEET_TYPE_OBJECT]
374
+
375
+ @property
376
+ def worksheets(self) -> List[Sheet]:
377
+ """
378
+ Get list of only the non-chart sheets in the workbook.
379
+ """
380
+ return [s for s in self.sheets if s.type == sheet.SHEET_TYPE_REGULAR]
381
+
382
+ def __str__(self) -> str:
383
+ return 'Workbook' + str(self.sheetnames)
384
+
385
+ def save(self, filepath: Union[str, Path], *, compress: int = 9) -> None:
386
+ """
387
+ Save the workbook to `filepath`.
388
+
389
+ :param compress: The level of compression to apply to the file. A value between 0 (no compression, but still
390
+ write it as a gzip-compressed Gnumeric file) and 9 (slowest but most compressed; default). A `False` value
391
+ will write a uncompressed Gnumeric file (i.e. `.xml`).
392
+ """
393
+ for s in self.sheets:
394
+ s._clean_data()
395
+
396
+ xml = etree.tostring(self.__root)
397
+
398
+ if compress is False:
399
+ with open(filepath, mode='wb') as fout:
400
+ fout.write(xml)
401
+ else:
402
+ with gzip.open(filepath, mode='wb', compresslevel=compress) as fout:
403
+ fout.write(xml)
404
+
405
+ @classmethod
406
+ def load_workbook(clas, filepath: Union[str, Path]) -> Self:
407
+ """
408
+ Open the given filepath and return the workbook.
409
+
410
+ Handles both uncompressed (`.xml`) and compressed (`.gnumeric`) Gnumeric files.
411
+ """
412
+ filepath = str(filepath)
413
+
414
+ if filepath.lower().endswith('.xml'):
415
+ open_method = open
416
+ else:
417
+ open_method = gzip.open
418
+
419
+ with open_method(filepath, mode='rb') as fin:
420
+ contents = fin.read()
421
+
422
+ root = etree.fromstring(contents)
423
+ return Workbook(root)
@@ -0,0 +1,55 @@
1
+ Metadata-Version: 2.4
2
+ Name: gnumeric
3
+ Version: 0.1.0
4
+ Summary: A python library for reading and writing Gnumeric files
5
+ Author: Michael Lipschultz
6
+ Author-email: Michael Lipschultz <michael.lipschultz@gmail.com>
7
+ License-Expression: GPL-3.0-only
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.11
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Classifier: Programming Language :: Python :: 3.14
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
16
+ Requires-Dist: lxml>=6.1.1,<7
17
+ Requires-Dist: python-dateutil>=2.9.0.post0,<3
18
+ Requires-Dist: lark>=1.3.1,<2
19
+ Requires-Python: >=3.11, <4
20
+ Project-URL: Homepage, https://github.com/lipschultz/gnumeric-py
21
+ Description-Content-Type: text/markdown
22
+
23
+ # Gnumeric-py
24
+
25
+ A python library for reading and writing [Gnumeric](http://www.gnumeric.org/) files.
26
+
27
+ ## Quick Start
28
+
29
+ Open an existing workbook and explore what's available:
30
+
31
+ ```python
32
+ from gnumeric.workbook import Workbook
33
+
34
+ wb = Workbook.load_workbook('samples/test.gnumeric')
35
+
36
+ # Get the active sheet
37
+ ws = wb.get_active_sheet() # can also use wb.active
38
+
39
+ # Get a sheet by name
40
+ ws_1 = wb.get_sheet_by_name('Sheet1')
41
+
42
+ # Get cell at A1
43
+ cell_a1 = ws['A1']
44
+ cell_a1 = ws[(0, 0)] # zero-indexed (row, column)
45
+
46
+ # Get the value in the cell
47
+ cell_a1.get_value()
48
+ ```
49
+
50
+ ## Unsupported Features
51
+
52
+ Currently, this library does not support:
53
+
54
+ 1. Evaluating almost all expressions
55
+ 2. Graphs
@@ -0,0 +1,17 @@
1
+ gnumeric/.constants.json,sha256=0wdaGC2sIzoUd5BpCVB-GyaaFeX7BYKZuz01D4Pis_g,276
2
+ gnumeric/__init__.py,sha256=LtCykaf_DJAhJBUA93pGCP4LGFSfLE14xhunFHv_QuE,1375
3
+ gnumeric/cell.py,sha256=tHplEycZa21gE6lEpPMP_EWO7h4gvF-nSOiLrzFgthg,10359
4
+ gnumeric/evaluation_errors.py,sha256=Ms_Avwlavn4yIpDoj7Xz5cMllpG3lzdfkgXoozQ2tx0,553
5
+ gnumeric/exceptions.py,sha256=301N-6S7louOyRaKkNOJ_SIpisozRtcxKuj64_GE5FI,1377
6
+ gnumeric/expression.py,sha256=j61D-lIsI_Dp6grKVKuF_IY0rghcbTf-Oywzph5r65M,5650
7
+ gnumeric/expression_evaluation.py,sha256=zibx5g8y3cBQKePhGgWYGGpLkl-2ey9Hs2qKOlpvBMM,9942
8
+ gnumeric/formula_functions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ gnumeric/formula_functions/argument_helpers.py,sha256=BTBzJSMJIviEgtDUcg5lvCEjm5pMu3ZlM5Iht8-lj3g,703
10
+ gnumeric/formula_functions/mathematics.py,sha256=VyiFPXDDVacUEm4XDVIGgD2a6aT8S5fOOrmRiWOzzCo,468
11
+ gnumeric/formula_functions/statistics.py,sha256=VR6QJpjoGXVy2vyrC4CX9B-WOv-mTbIfFXzXq3dv4_w,487
12
+ gnumeric/sheet.py,sha256=Rb6m4FgH78vlxv8htE7u-x9ce3oLCaZ_t2drPFAqeYo,24670
13
+ gnumeric/utils.py,sha256=Zn6RlLqgwv6t04aPTN1Ii97DNYSOL2UkzpZRJDRAk9s,4144
14
+ gnumeric/workbook.py,sha256=fd83JFlzO3sOg7qqXRiaRS18ZFmsfA4at_k8A2oQeNM,15706
15
+ gnumeric-0.1.0.dist-info/WHEEL,sha256=XV0cjMrO7zXhVAIyyc8aFf1VjZ33Fen4IiJk5zFlC3g,80
16
+ gnumeric-0.1.0.dist-info/METADATA,sha256=h3iaBl_vGz3VMeGvCIrvNxhyy8Rbvk7iJz0z_SatNNs,1573
17
+ gnumeric-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.9.26
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any