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/.constants.json +8 -0
- gnumeric/__init__.py +41 -0
- gnumeric/cell.py +294 -0
- gnumeric/evaluation_errors.py +24 -0
- gnumeric/exceptions.py +49 -0
- gnumeric/expression.py +151 -0
- gnumeric/expression_evaluation.py +315 -0
- gnumeric/formula_functions/__init__.py +0 -0
- gnumeric/formula_functions/argument_helpers.py +21 -0
- gnumeric/formula_functions/mathematics.py +21 -0
- gnumeric/formula_functions/statistics.py +23 -0
- gnumeric/sheet.py +642 -0
- gnumeric/utils.py +131 -0
- gnumeric/workbook.py +423 -0
- gnumeric-0.1.0.dist-info/METADATA +55 -0
- gnumeric-0.1.0.dist-info/RECORD +17 -0
- gnumeric-0.1.0.dist-info/WHEEL +4 -0
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>"Sheet1"</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="&[TAB]" Right=""/>
|
|
121
|
+
<gnm:Footer Left="" Middle="Page &[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,,
|