pyOfficeEditor 0.2.2__tar.gz → 0.3.0__tar.gz
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.
- {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/PKG-INFO +66 -11
- {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/README.md +65 -10
- {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/docs/architecture.md +176 -8
- pyofficeeditor-0.3.0/docs/changelog.md +797 -0
- {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/pyproject.toml +1 -1
- {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/_xml.py +41 -4
- {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/__init__.py +61 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/__init__.py +17 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/catalog.py +72 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/dates.py +214 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/engine.py +549 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/evaluator.py +797 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/functions/__init__.py +47 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/functions/aggregate.py +370 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/functions/arithmetic.py +756 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/functions/arrays.py +451 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/functions/common.py +277 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/functions/conditional.py +137 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/functions/database.py +266 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/functions/dates.py +442 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/functions/distributions.py +1270 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/functions/engineering.py +929 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/functions/financial.py +1555 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/functions/information.py +247 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/functions/lambdas.py +175 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/functions/logical.py +154 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/functions/lookup.py +427 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/functions/matrix.py +130 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/functions/statistics.py +727 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/functions/subtotal.py +243 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/functions/text.py +642 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/lexer.py +479 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/nodes.py +419 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/numbers.py +199 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/parser.py +245 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/precise.py +431 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/registry.py +157 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/special.py +521 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/values.py +402 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_cellstyles.py +256 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_chartbuild.py +384 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_charts.py +267 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_collate.py +200 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_comments.py +626 -0
- {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/_conditional.py +9 -4
- {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/_dimensions.py +92 -6
- {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/_dxf.py +7 -2
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_filters.py +1072 -0
- {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/_formats.py +3 -1
- {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/_hyperlinks.py +11 -6
- {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/_names.py +16 -8
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_numfmt.py +1351 -0
- {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/_pagesetup.py +3 -2
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_pictures.py +170 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_pivots.py +137 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_richtext.py +169 -0
- {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/_rowcol.py +342 -16
- {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/_schema.py +11 -0
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_shapes.py +1541 -0
- {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/_sharedstrings.py +41 -15
- {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/_styles.py +202 -14
- {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/_tables.py +44 -17
- {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/_tokens.py +5 -2
- {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/_validation.py +14 -7
- {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/_values.py +58 -14
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_xstring.py +87 -0
- {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/workbook.py +357 -34
- pyofficeeditor-0.3.0/src/pyofficeeditor/excel/worksheet.py +4195 -0
- {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/exceptions.py +6 -0
- {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/opc.py +6 -2
- pyofficeeditor-0.3.0/tests/fixtures/excel/README.md +117 -0
- pyofficeeditor-0.2.2/docs/changelog.md +0 -213
- pyofficeeditor-0.2.2/src/pyofficeeditor/excel/_shapes.py +0 -553
- pyofficeeditor-0.2.2/src/pyofficeeditor/excel/worksheet.py +0 -2246
- pyofficeeditor-0.2.2/tests/fixtures/excel/README.md +0 -72
- {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/.gitignore +0 -0
- {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/LICENSE.md +0 -0
- {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/__init__.py +0 -0
- {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/_zip.py +0 -0
- {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/_addresses.py +0 -0
- {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/_formulas.py +0 -0
- {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/_protection.py +0 -0
- {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/_reference.py +0 -0
- {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pyOfficeEditor
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Edit the document surface of Excel, Word, PowerPoint and Access in pure Python, no dependencies.
|
|
5
5
|
Project-URL: Homepage, https://github.com/WilliamSmithEdward/pyOfficeEditor
|
|
6
6
|
Project-URL: Repository, https://github.com/WilliamSmithEdward/pyOfficeEditor
|
|
@@ -50,7 +50,23 @@ cell, the formula, the paragraph, the slide, the table, the query.
|
|
|
50
50
|
> Excel. Conditional formatting reads and writes every rule family Excel has.
|
|
51
51
|
> Data validation covers every rule Excel has, dropdowns included. Sheets
|
|
52
52
|
> protect, hide, colour their tabs, set their own view, and print the way
|
|
53
|
-
> you set them up. Hyperlinks, outline grouping, and the shapes on a sheet
|
|
53
|
+
> you set them up. Hyperlinks, outline grouping, and the shapes on a sheet:
|
|
54
|
+
> AutoShapes, text boxes, lines and all nine Forms controls read, added,
|
|
55
|
+
> removed and pointed at a macro, with a control's linked cell, list range
|
|
56
|
+
> and value. Autofilters, on a sheet or a table, read and write every
|
|
57
|
+
> criterion but colour and icon, and hide the rows they exclude, because
|
|
58
|
+
> Excel does not recompute a filter when the workbook opens. A cell's text
|
|
59
|
+
> is what Excel shows for it, number format and all. Notes and threaded
|
|
60
|
+
> comments, replies included, read and write the way Excel writes them,
|
|
61
|
+
> and so do pictures, text in more than one font, and named cell styles,
|
|
62
|
+
> all of Excel's own among them. Charts are read, series by series, and
|
|
63
|
+
> added in eight kinds as Excel's Insert Chart writes them; their
|
|
64
|
+
> references move with the cells they read, and so do pivot tables and
|
|
65
|
+
> their caches, refusing the edits Excel refuses. Formulas calculate:
|
|
66
|
+
> 493 of Excel's 525 functions, held to 10,958 formulas Excel
|
|
67
|
+
> calculated, to the last bit for all but the few functions the tests
|
|
68
|
+
> name, with the results written into the cells as Excel's
|
|
69
|
+
> recalculation would write them.
|
|
54
70
|
> Rows and columns can be inserted and deleted, with every reference in the
|
|
55
71
|
> workbook following or breaking exactly as Excel breaks it, `A:A` and `2:4`
|
|
56
72
|
> included. Nothing about a sheet makes that refuse any more: validation,
|
|
@@ -59,7 +75,9 @@ cell, the formula, the paragraph, the slide, the table, the query.
|
|
|
59
75
|
|
|
60
76
|
```python
|
|
61
77
|
import datetime as dt
|
|
62
|
-
from pyofficeeditor.excel import
|
|
78
|
+
from pyofficeeditor.excel import (
|
|
79
|
+
Border, Dxf, FilterColumn, Workbook, cell_is, criteria, gradient,
|
|
80
|
+
)
|
|
63
81
|
|
|
64
82
|
with Workbook.open("orders.xlsx") as book:
|
|
65
83
|
sheet = book["Data"]
|
|
@@ -84,6 +102,8 @@ with Workbook.open("orders.xlsx") as book:
|
|
|
84
102
|
table = sheet.add_table("Sales", "A1:F20", totals_row=True)
|
|
85
103
|
table.column_names # from the header row
|
|
86
104
|
table.data_range # excludes header and totals
|
|
105
|
+
sheet.set_table_filter("Sales", [FilterColumn(1, criteria(">100"))])
|
|
106
|
+
# and hides the rows it drops
|
|
87
107
|
|
|
88
108
|
sheet.set_row_height(1, 24) # points, exact
|
|
89
109
|
sheet.set_column_hidden(4, True)
|
|
@@ -101,6 +121,9 @@ with Workbook.open("orders.xlsx") as book:
|
|
|
101
121
|
summary["A1"].formula = "=SUM(Data!D2:D5)"
|
|
102
122
|
book.add_defined_name("Totals", "Data!$D$2:$D$5")
|
|
103
123
|
book.rename_sheet("Data", "Q1 Data") # formulas and names both follow
|
|
124
|
+
|
|
125
|
+
summary.evaluate("SUM(Totals)/4") # what a formula would give
|
|
126
|
+
book.calculate() # every formula, results cached
|
|
104
127
|
book.save()
|
|
105
128
|
```
|
|
106
129
|
|
|
@@ -163,29 +186,54 @@ data descriptors.
|
|
|
163
186
|
| _values the six cell encodings, and serial dates |
|
|
164
187
|
| _styles number formats, which is how a date is |
|
|
165
188
|
| told from a number |
|
|
189
|
+
| _numfmt what a number format shows, rounding and |
|
|
190
|
+
| Excel's fictional 1900 leap day included |
|
|
191
|
+
| _collate Windows word sort, the order and the |
|
|
192
|
+
| equality a filter compares text by |
|
|
193
|
+
| _filters autofilters, and which rows they hide: |
|
|
194
|
+
| Excel does not recompute one on open |
|
|
166
195
|
| _formats fonts, fills, borders, alignment, as |
|
|
167
196
|
| immutable values |
|
|
197
|
+
| _cellstyles named styles, and Excel's definitions of |
|
|
198
|
+
| its own |
|
|
168
199
|
| _tables ListObjects: their own parts and wiring |
|
|
169
200
|
| _dimensions widths, heights, hiding, frozen panes |
|
|
170
|
-
| _names defined names, and the rules
|
|
171
|
-
|
|
|
172
|
-
|
|
|
173
|
-
|
|
|
201
|
+
| _names defined names, and the naming rules |
|
|
202
|
+
| tables share with them |
|
|
203
|
+
| _shapes shapes, and the grid that places a |
|
|
204
|
+
| control |
|
|
205
|
+
| _comments notes and threads, and the box each gets |
|
|
206
|
+
| _pictures images, sized as Excel sizes them |
|
|
207
|
+
| _charts what each chart plots, and chart sheets |
|
|
208
|
+
| _chartbuild charts written as Excel's Insert Chart |
|
|
209
|
+
| writes them |
|
|
210
|
+
| _pivots where each pivot table is, and what its |
|
|
211
|
+
| cache reads |
|
|
212
|
+
| _richtext runs of text in several fonts, read as |
|
|
213
|
+
| Excel shows them, not as marked up |
|
|
214
|
+
| _pagesetup margins in inches, and the three header |
|
|
215
|
+
| boxes coded into one string |
|
|
174
216
|
| _validation what a cell accepts, and the inverted |
|
|
175
217
|
| attribute behind its dropdown |
|
|
176
218
|
| _conditional cfRules, and the compatibility formula |
|
|
177
219
|
| that makes them fire |
|
|
178
220
|
| _dxf differential formats: what a rule paints |
|
|
179
|
-
| _addresses the five notations an address is spelled
|
|
180
|
-
| in, two of them zero-based
|
|
181
|
-
| _rowcol inserting and deleting rows and columns,
|
|
221
|
+
| _addresses the five notations an address is spelled |
|
|
222
|
+
| in, two of them zero-based |
|
|
223
|
+
| _rowcol inserting and deleting rows and columns, |
|
|
182
224
|
| and moving everything that records a |
|
|
183
225
|
| cell address |
|
|
184
|
-
| _tokens a formula, broken into editable pieces
|
|
226
|
+
| _tokens a formula, broken into editable pieces |
|
|
185
227
|
| _formulas shifting and breaking references, for |
|
|
186
228
|
| shared formulas, a renamed sheet, and |
|
|
187
229
|
| the #REF! a deletion leaves behind |
|
|
230
|
+
| _calc/ the formula engine: a parser, an |
|
|
231
|
+
| evaluator with Excel's arithmetic, the |
|
|
232
|
+
| functions, and whole-workbook |
|
|
233
|
+
| calculation |
|
|
188
234
|
| _sharedstrings the per-workbook string table |
|
|
235
|
+
| _xstring text as SpreadsheetML spells it: _xHHHH_ |
|
|
236
|
+
| for what XML cannot carry |
|
|
189
237
|
| _schema where a child element has to go |
|
|
190
238
|
+--------------------------------------------------------+
|
|
191
239
|
| word / powerpoint / access to follow, in that order |
|
|
@@ -333,6 +381,13 @@ RUN_LIVE_EXCEL=1 python -m pytest -m live -p no:randomly
|
|
|
333
381
|
|
|
334
382
|
Windows and Excel only. Everything else in the suite runs anywhere.
|
|
335
383
|
|
|
384
|
+
## Built with this
|
|
385
|
+
|
|
386
|
+
[xlide-mcp](https://github.com/WilliamSmithEdward/xlide_mcp)
|
|
387
|
+
is an MCP server built on this. This library is what answers when an
|
|
388
|
+
agent asks it for a cell, a formula, a table or a conditional rule, and
|
|
389
|
+
what writes the change back with the rest of the package left alone.
|
|
390
|
+
|
|
336
391
|
## License
|
|
337
392
|
|
|
338
393
|
MIT. See [LICENSE.md](LICENSE.md).
|
|
@@ -13,7 +13,23 @@ cell, the formula, the paragraph, the slide, the table, the query.
|
|
|
13
13
|
> Excel. Conditional formatting reads and writes every rule family Excel has.
|
|
14
14
|
> Data validation covers every rule Excel has, dropdowns included. Sheets
|
|
15
15
|
> protect, hide, colour their tabs, set their own view, and print the way
|
|
16
|
-
> you set them up. Hyperlinks, outline grouping, and the shapes on a sheet
|
|
16
|
+
> you set them up. Hyperlinks, outline grouping, and the shapes on a sheet:
|
|
17
|
+
> AutoShapes, text boxes, lines and all nine Forms controls read, added,
|
|
18
|
+
> removed and pointed at a macro, with a control's linked cell, list range
|
|
19
|
+
> and value. Autofilters, on a sheet or a table, read and write every
|
|
20
|
+
> criterion but colour and icon, and hide the rows they exclude, because
|
|
21
|
+
> Excel does not recompute a filter when the workbook opens. A cell's text
|
|
22
|
+
> is what Excel shows for it, number format and all. Notes and threaded
|
|
23
|
+
> comments, replies included, read and write the way Excel writes them,
|
|
24
|
+
> and so do pictures, text in more than one font, and named cell styles,
|
|
25
|
+
> all of Excel's own among them. Charts are read, series by series, and
|
|
26
|
+
> added in eight kinds as Excel's Insert Chart writes them; their
|
|
27
|
+
> references move with the cells they read, and so do pivot tables and
|
|
28
|
+
> their caches, refusing the edits Excel refuses. Formulas calculate:
|
|
29
|
+
> 493 of Excel's 525 functions, held to 10,958 formulas Excel
|
|
30
|
+
> calculated, to the last bit for all but the few functions the tests
|
|
31
|
+
> name, with the results written into the cells as Excel's
|
|
32
|
+
> recalculation would write them.
|
|
17
33
|
> Rows and columns can be inserted and deleted, with every reference in the
|
|
18
34
|
> workbook following or breaking exactly as Excel breaks it, `A:A` and `2:4`
|
|
19
35
|
> included. Nothing about a sheet makes that refuse any more: validation,
|
|
@@ -22,7 +38,9 @@ cell, the formula, the paragraph, the slide, the table, the query.
|
|
|
22
38
|
|
|
23
39
|
```python
|
|
24
40
|
import datetime as dt
|
|
25
|
-
from pyofficeeditor.excel import
|
|
41
|
+
from pyofficeeditor.excel import (
|
|
42
|
+
Border, Dxf, FilterColumn, Workbook, cell_is, criteria, gradient,
|
|
43
|
+
)
|
|
26
44
|
|
|
27
45
|
with Workbook.open("orders.xlsx") as book:
|
|
28
46
|
sheet = book["Data"]
|
|
@@ -47,6 +65,8 @@ with Workbook.open("orders.xlsx") as book:
|
|
|
47
65
|
table = sheet.add_table("Sales", "A1:F20", totals_row=True)
|
|
48
66
|
table.column_names # from the header row
|
|
49
67
|
table.data_range # excludes header and totals
|
|
68
|
+
sheet.set_table_filter("Sales", [FilterColumn(1, criteria(">100"))])
|
|
69
|
+
# and hides the rows it drops
|
|
50
70
|
|
|
51
71
|
sheet.set_row_height(1, 24) # points, exact
|
|
52
72
|
sheet.set_column_hidden(4, True)
|
|
@@ -64,6 +84,9 @@ with Workbook.open("orders.xlsx") as book:
|
|
|
64
84
|
summary["A1"].formula = "=SUM(Data!D2:D5)"
|
|
65
85
|
book.add_defined_name("Totals", "Data!$D$2:$D$5")
|
|
66
86
|
book.rename_sheet("Data", "Q1 Data") # formulas and names both follow
|
|
87
|
+
|
|
88
|
+
summary.evaluate("SUM(Totals)/4") # what a formula would give
|
|
89
|
+
book.calculate() # every formula, results cached
|
|
67
90
|
book.save()
|
|
68
91
|
```
|
|
69
92
|
|
|
@@ -126,29 +149,54 @@ data descriptors.
|
|
|
126
149
|
| _values the six cell encodings, and serial dates |
|
|
127
150
|
| _styles number formats, which is how a date is |
|
|
128
151
|
| told from a number |
|
|
152
|
+
| _numfmt what a number format shows, rounding and |
|
|
153
|
+
| Excel's fictional 1900 leap day included |
|
|
154
|
+
| _collate Windows word sort, the order and the |
|
|
155
|
+
| equality a filter compares text by |
|
|
156
|
+
| _filters autofilters, and which rows they hide: |
|
|
157
|
+
| Excel does not recompute one on open |
|
|
129
158
|
| _formats fonts, fills, borders, alignment, as |
|
|
130
159
|
| immutable values |
|
|
160
|
+
| _cellstyles named styles, and Excel's definitions of |
|
|
161
|
+
| its own |
|
|
131
162
|
| _tables ListObjects: their own parts and wiring |
|
|
132
163
|
| _dimensions widths, heights, hiding, frozen panes |
|
|
133
|
-
| _names defined names, and the rules
|
|
134
|
-
|
|
|
135
|
-
|
|
|
136
|
-
|
|
|
164
|
+
| _names defined names, and the naming rules |
|
|
165
|
+
| tables share with them |
|
|
166
|
+
| _shapes shapes, and the grid that places a |
|
|
167
|
+
| control |
|
|
168
|
+
| _comments notes and threads, and the box each gets |
|
|
169
|
+
| _pictures images, sized as Excel sizes them |
|
|
170
|
+
| _charts what each chart plots, and chart sheets |
|
|
171
|
+
| _chartbuild charts written as Excel's Insert Chart |
|
|
172
|
+
| writes them |
|
|
173
|
+
| _pivots where each pivot table is, and what its |
|
|
174
|
+
| cache reads |
|
|
175
|
+
| _richtext runs of text in several fonts, read as |
|
|
176
|
+
| Excel shows them, not as marked up |
|
|
177
|
+
| _pagesetup margins in inches, and the three header |
|
|
178
|
+
| boxes coded into one string |
|
|
137
179
|
| _validation what a cell accepts, and the inverted |
|
|
138
180
|
| attribute behind its dropdown |
|
|
139
181
|
| _conditional cfRules, and the compatibility formula |
|
|
140
182
|
| that makes them fire |
|
|
141
183
|
| _dxf differential formats: what a rule paints |
|
|
142
|
-
| _addresses the five notations an address is spelled
|
|
143
|
-
| in, two of them zero-based
|
|
144
|
-
| _rowcol inserting and deleting rows and columns,
|
|
184
|
+
| _addresses the five notations an address is spelled |
|
|
185
|
+
| in, two of them zero-based |
|
|
186
|
+
| _rowcol inserting and deleting rows and columns, |
|
|
145
187
|
| and moving everything that records a |
|
|
146
188
|
| cell address |
|
|
147
|
-
| _tokens a formula, broken into editable pieces
|
|
189
|
+
| _tokens a formula, broken into editable pieces |
|
|
148
190
|
| _formulas shifting and breaking references, for |
|
|
149
191
|
| shared formulas, a renamed sheet, and |
|
|
150
192
|
| the #REF! a deletion leaves behind |
|
|
193
|
+
| _calc/ the formula engine: a parser, an |
|
|
194
|
+
| evaluator with Excel's arithmetic, the |
|
|
195
|
+
| functions, and whole-workbook |
|
|
196
|
+
| calculation |
|
|
151
197
|
| _sharedstrings the per-workbook string table |
|
|
198
|
+
| _xstring text as SpreadsheetML spells it: _xHHHH_ |
|
|
199
|
+
| for what XML cannot carry |
|
|
152
200
|
| _schema where a child element has to go |
|
|
153
201
|
+--------------------------------------------------------+
|
|
154
202
|
| word / powerpoint / access to follow, in that order |
|
|
@@ -296,6 +344,13 @@ RUN_LIVE_EXCEL=1 python -m pytest -m live -p no:randomly
|
|
|
296
344
|
|
|
297
345
|
Windows and Excel only. Everything else in the suite runs anywhere.
|
|
298
346
|
|
|
347
|
+
## Built with this
|
|
348
|
+
|
|
349
|
+
[xlide-mcp](https://github.com/WilliamSmithEdward/xlide_mcp)
|
|
350
|
+
is an MCP server built on this. This library is what answers when an
|
|
351
|
+
agent asks it for a cell, a formula, a table or a conditional rule, and
|
|
352
|
+
what writes the change back with the rest of the package left alone.
|
|
353
|
+
|
|
299
354
|
## License
|
|
300
355
|
|
|
301
356
|
MIT. See [LICENSE.md](LICENSE.md).
|
|
@@ -67,16 +67,44 @@ Each layer knows the layer below it and not the layer above.
|
|
|
67
67
|
| rather than permissions |
|
|
68
68
|
| _pagesetup margins in inches, headers as one string |
|
|
69
69
|
| _hyperlinks links, whose address is in a relationship |
|
|
70
|
+
| _filters autofilters, and which rows they hide: |
|
|
71
|
+
| Excel does not recompute one on open |
|
|
72
|
+
| _numfmt what a number format shows, rounding and |
|
|
73
|
+
| the fictional 1900 leap day included |
|
|
74
|
+
| _collate Windows word sort, the order and the |
|
|
75
|
+
| equality a filter compares text by |
|
|
70
76
|
| _shapes shapes, and the grid a form control needs |
|
|
77
|
+
| _comments notes and threads, and the box each gets |
|
|
78
|
+
| _pictures images, sized as Excel sizes them |
|
|
79
|
+
| _charts what each chart plots, and chart sheets |
|
|
80
|
+
| _chartbuild charts written as Excel's Insert Chart |
|
|
81
|
+
| writes them |
|
|
82
|
+
| _pivots where each pivot table is, and what its |
|
|
83
|
+
| cache reads |
|
|
84
|
+
| _richtext runs of text in several fonts, read as |
|
|
85
|
+
| Excel shows them, not as marked up |
|
|
71
86
|
| _conditional cfRules, and the compatibility formula |
|
|
72
87
|
| that makes them fire |
|
|
73
88
|
| _dxf differential formats: what a rule paints |
|
|
74
89
|
| _styles the five style tables; is this a date? |
|
|
75
90
|
| _formats fonts, fills, borders, alignment, as |
|
|
76
91
|
| immutable values |
|
|
92
|
+
| _cellstyles named styles, and Excel's definitions of |
|
|
93
|
+
| its own |
|
|
77
94
|
| _formulas reference shifting for shared formulas, |
|
|
78
95
|
| and repointing a renamed sheet |
|
|
96
|
+
| _calc/ the formula engine |
|
|
97
|
+
| lexer, parser, nodes formula text into a tree, |
|
|
98
|
+
| with Excel's precedence |
|
|
99
|
+
| values, numbers, dates, precise Excel's scalars, |
|
|
100
|
+
| coercion, and arithmetic past IEEE's |
|
|
101
|
+
| evaluator, registry, functions/ a tree evaluated |
|
|
102
|
+
| against a workbook, function by family |
|
|
103
|
+
| engine every formula in dependency order, |
|
|
104
|
+
| results written back |
|
|
79
105
|
| _sharedstrings the per-workbook string table |
|
|
106
|
+
| _xstring text as SpreadsheetML spells it, with |
|
|
107
|
+
| _xHHHH_ for what XML cannot carry |
|
|
80
108
|
| _reference A1 notation, bijective base-26 |
|
|
81
109
|
| _schema the child order CT_Worksheet and |
|
|
82
110
|
| CT_Workbook require |
|
|
@@ -112,7 +140,9 @@ Each layer knows the layer below it and not the layer above.
|
|
|
112
140
|
something from the container, `opc.py` grows a method.
|
|
113
141
|
- Inside `excel/`, the four private modules are pure: `_reference`,
|
|
114
142
|
`_styles`, `_formulas` and `_values` know about elements and strings, not
|
|
115
|
-
about packages or files. `workbook.py` is the only one that does.
|
|
143
|
+
about packages or files. `workbook.py` is the only one that does. So are
|
|
144
|
+
`_numfmt`, `_collate` and `_filters`: the worksheet hands a filter its
|
|
145
|
+
cells and the date, and the filter decides rows without touching it.
|
|
116
146
|
- `Worksheet` owns the part and does the work, addressed by `CellRef`.
|
|
117
147
|
`Cell` and `Range` are views that delegate to it, so nothing is reachable
|
|
118
148
|
only through a cell. That is why `Worksheet.get_value` and friends are
|
|
@@ -219,6 +249,21 @@ The Excel surface is exported from `pyofficeeditor.excel`:
|
|
|
219
249
|
| `Styles`, `SharedStrings` | `excel/_styles.py`, `excel/_sharedstrings.py` | the shared parts |
|
|
220
250
|
| `column_letter`, `column_index` | `excel/_reference.py` | bijective base-26 |
|
|
221
251
|
| `MAX_ROW`, `MAX_COLUMN` | `excel/_reference.py` | Excel's real limits |
|
|
252
|
+
| `AutoFilter`, `FilterColumn`, `ValueFilter`, `CustomFilter`, `Comparison`, `FilterOperator`, `Top10Filter`, `DynamicFilter`, `DateGroup`, `OpaqueCriterion`, `criteria` | `excel/_filters.py` | a filter's criteria, validated as Excel validates them |
|
|
253
|
+
| `FilterOutcome` | `excel/_filters.py` | which rows applying a filter hid, showed and left alone |
|
|
254
|
+
| `Comment` | `excel/_comments.py` | a note on a cell: text, author, and whether it shows |
|
|
255
|
+
| `ThreadedComment`, `Reply` | `excel/_comments.py` | a conversation on a cell, and each answer in it |
|
|
256
|
+
| `TextRun` | `excel/_richtext.py` | part of a cell's text, and the font it shows in |
|
|
257
|
+
| `CellStyle` | `excel/_cellstyles.py` | a named cell style: its format, and what of a cell it sets |
|
|
258
|
+
| `Chart`, `ChartSeries` | `excel/_charts.py` | what a chart plots, and each series' references |
|
|
259
|
+
| `ChartSheet` | `excel/_charts.py` | a tab that is one chart and holds no cells |
|
|
260
|
+
| `PivotTable` | `excel/_pivots.py` | where a pivot table is, and what its cache was read from |
|
|
261
|
+
| `format_value` | `excel/_numfmt.py` | the text Excel shows for a value under a format code |
|
|
262
|
+
| `Calculation` | `excel/_calc/engine.py` | what `Workbook.calculate()` did, and which formulas kept their cached value |
|
|
263
|
+
| `FormulaSyntaxError` | `excel/_calc/lexer.py` | formula text Excel would refuse, and where |
|
|
264
|
+
|
|
265
|
+
`UnsupportedFormulaError`, in `exceptions.py`, is what `Worksheet.evaluate`
|
|
266
|
+
raises for a formula needing something the engine does not have.
|
|
222
267
|
|
|
223
268
|
`opc.py` also exports the path helpers (`normalize_part_name`,
|
|
224
269
|
`rels_part_for`, `resolve_target`, `relative_target`) and the content-type
|
|
@@ -300,11 +345,41 @@ tests/
|
|
|
300
345
|
test_excel_settings.py protection, tab colour, view, visibility
|
|
301
346
|
test_excel_pagesetup.py margins, orientation, headers, print area
|
|
302
347
|
test_excel_links.py hyperlinks and outline grouping
|
|
348
|
+
test_excel_filters.py autofilters on sheets and tables, and the
|
|
349
|
+
rows they hide
|
|
350
|
+
test_excel_filter_semantics.py which rows each criterion keeps, case
|
|
351
|
+
by case against filter_semantics.json
|
|
352
|
+
test_excel_numfmt.py what a number format shows, against
|
|
353
|
+
number_formats.json
|
|
303
354
|
test_excel_shapes.py shapes, against what Excel said of them
|
|
355
|
+
test_excel_controls.py what a form control is wired to
|
|
356
|
+
test_excel_shapes_write.py adding, removing and rewiring shapes
|
|
357
|
+
test_excel_comments.py notes and threads, against what Excel said
|
|
358
|
+
test_excel_pictures.py pictures, against what Excel said of them
|
|
359
|
+
test_excel_xstring.py _xHHHH_ escapes, character by character,
|
|
360
|
+
against what Excel wrote and read
|
|
361
|
+
test_excel_escapes.py that text everywhere a workbook keeps it,
|
|
362
|
+
against escapes_answers.json
|
|
363
|
+
test_excel_richtext.py text in several fonts, against the fonts
|
|
364
|
+
Excel showed and the entries it wrote
|
|
365
|
+
test_excel_cellstyles.py named styles, against the cells Excel
|
|
366
|
+
styled itself
|
|
367
|
+
test_excel_charts.py charts and chart sheets, and every
|
|
368
|
+
reference after nine edits, against the
|
|
369
|
+
files Excel saved after the same edits
|
|
370
|
+
test_excel_pivots.py pivot tables through nineteen edits, and
|
|
371
|
+
the ones Excel refuses
|
|
372
|
+
test_excel_chartbuild.py charts added, against the parts Excel
|
|
373
|
+
wrote for the same charts
|
|
304
374
|
test_excel_dxf.py differential formats and the dxfs table
|
|
375
|
+
test_excel_calc.py the formula parser, Excel's arithmetic,
|
|
376
|
+
and calculating a whole workbook
|
|
377
|
+
test_excel_formula_corpus.py every formula in formulas.xlsx,
|
|
378
|
+
calculated and held to Excel's result
|
|
305
379
|
test_excel_live_gate.py real Excel, opt-in
|
|
306
|
-
fixtures/excel/
|
|
307
|
-
|
|
380
|
+
fixtures/excel/ twenty-one Excel-authored packages:
|
|
381
|
+
three sourced, eighteen scripted, and
|
|
382
|
+
three measured corpora; see its README
|
|
308
383
|
```
|
|
309
384
|
|
|
310
385
|
- **Always** run pytest with `-p no:randomly` to keep ordering reproducible.
|
|
@@ -312,14 +387,17 @@ tests/
|
|
|
312
387
|
merge: `pyright src tests`.
|
|
313
388
|
- **Ruff** must pass: `ruff check src tests scripts`.
|
|
314
389
|
- New behavior lands with its test in the same commit.
|
|
315
|
-
- The suite needs no Office installation. It runs against
|
|
390
|
+
- The suite needs no Office installation. It runs against twenty-one committed
|
|
316
391
|
Excel-authored packages, an `.xlsb` among them, and against
|
|
317
392
|
openpyxl-authored ones generated during the run, because a reader that
|
|
318
393
|
only ever sees one producer's output encodes that producer's habits as
|
|
319
394
|
rules.
|
|
320
395
|
- Richer Excel-authored fixtures come from
|
|
321
396
|
`scripts/build_excel_fixtures.py`, which drives real Excel through
|
|
322
|
-
`pyvbaharness`. Tests needing them skip when they are absent.
|
|
397
|
+
`pyvbaharness`. Tests needing them skip when they are absent. The three
|
|
398
|
+
measured corpora come from `scripts/measure_number_formats.py`,
|
|
399
|
+
`scripts/measure_filters.py` and `scripts/measure_formulas.py` the same
|
|
400
|
+
way.
|
|
323
401
|
|
|
324
402
|
### 8.1 The fidelity gate
|
|
325
403
|
|
|
@@ -470,9 +548,77 @@ the file. The stored number is exposed as-is, because a conversion right for
|
|
|
470
548
|
one font and quietly wrong for the rest is worse than none. Row heights carry
|
|
471
549
|
no such trap: they are points, and 24 stores as 24.
|
|
472
550
|
|
|
473
|
-
**A
|
|
474
|
-
|
|
475
|
-
|
|
551
|
+
**A row height needs its companion flag, and a column needs a width.** An
|
|
552
|
+
`ht` without `customHeight="1"` is ignored, so the value looks like it never
|
|
553
|
+
took. A `width` is honoured with or without `customWidth="1"`, but a `<col>`
|
|
554
|
+
with no `width` at all is a column of width 0: an entry carrying only
|
|
555
|
+
`outlineLevel`, `collapsed`, `style` or nothing shows as hidden, and stays
|
|
556
|
+
hidden when its outline is expanded. So every entry this library makes for a
|
|
557
|
+
column at the standard width carries that width, the one Excel writes: the
|
|
558
|
+
sheet's `defaultColWidth` when it has one, and otherwise a width fixed by the
|
|
559
|
+
Normal font, measured per font. Grouping columns hid them outright until this
|
|
560
|
+
was measured.
|
|
561
|
+
|
|
562
|
+
**A filter is stored twice, and Excel trusts the second copy.** The criteria
|
|
563
|
+
live in `<autoFilter>`, and which rows they hide lives on each row's `hidden`
|
|
564
|
+
flag. Excel does not recompute a filter when the workbook opens: it lights
|
|
565
|
+
the dropdowns from the first and shows the rows the second allows. So setting
|
|
566
|
+
a filter here decides every row, and that needs Excel's own rules. A value
|
|
567
|
+
list compares the text a cell *shows*, which is why `_numfmt` renders number
|
|
568
|
+
formats as Excel does, and compares it by Windows word sort, which `_collate`
|
|
569
|
+
reproduces. Both are held to corpora measured from Excel,
|
|
570
|
+
`number_formats.json` and `filter_semantics.json`, because the specification
|
|
571
|
+
states neither. A row that hangs on something not modelled, a colour filter
|
|
572
|
+
or a formula whose cached result an edit has made stale, is left as it was
|
|
573
|
+
rather than guessed at, and `FilterOutcome` says which rows those were.
|
|
574
|
+
|
|
575
|
+
**A chart reads the workbook through formulas.** Everything a chart plots,
|
|
576
|
+
a series' name, categories and values, and a title linked to a cell, is a
|
|
577
|
+
sheet-qualified reference in a `<c:f>` of the chart's own part. A chart on
|
|
578
|
+
any sheet, or on a chart sheet, may read from any other, so inserting or
|
|
579
|
+
deleting rows on one sheet has to visit every chart part in the workbook,
|
|
580
|
+
and renaming a sheet likewise. Measured, the references move exactly as a
|
|
581
|
+
cell's formula does, down to `Data!#REF!` for one deleted outright. Excel's
|
|
582
|
+
object model is no witness for that last case: it reports the old address
|
|
583
|
+
until the file is reopened, and then refuses to report the series, so the
|
|
584
|
+
fixture records the references in the files Excel saved.
|
|
585
|
+
|
|
586
|
+
**A pivot table is two parts away from its cells.** Its location is in its
|
|
587
|
+
own part and the range its cache was read from is in the cache's, and
|
|
588
|
+
neither is in the sheet. Excel refuses an insertion or deletion that cuts
|
|
589
|
+
through a pivot table, and so does this library, rather than guess at a
|
|
590
|
+
layout Excel would not produce; one that takes all of it deletes it, and its
|
|
591
|
+
cache when nothing else reads from that, as Excel does.
|
|
592
|
+
|
|
593
|
+
**A named style is defined only once something uses it.** A workbook's
|
|
594
|
+
`cellStyles` names each style it has and points at the style's format in
|
|
595
|
+
`cellStyleXfs`, whose `apply...="0"` attributes say what the style leaves
|
|
596
|
+
out. Excel's own styles are not in a workbook until a cell uses one, when
|
|
597
|
+
Excel writes one out from a definition of its own, and so does this library,
|
|
598
|
+
from Excel's definitions as Excel wrote them. Those definitions name the
|
|
599
|
+
theme's typefaces rather than the Normal style's, so they follow a change
|
|
600
|
+
of theme and not a change of Normal. A cell in a style carries a copy of what
|
|
601
|
+
the style sets, and its own `apply...="1"` marks where it differs.
|
|
602
|
+
|
|
603
|
+
**Text is not stored as XML stores it.** XML cannot carry most control
|
|
604
|
+
characters, and an attribute turns a line break into a space. SpreadsheetML
|
|
605
|
+
spells such a character `_xHHHH_` instead, and escapes the underscore of any
|
|
606
|
+
literal text that would read as one, so `_x0041_` in a cell is stored
|
|
607
|
+
`_x005F_x0041_`. Excel writes uppercase hex in element text, where a line
|
|
608
|
+
break stays a line break, and lowercase hex in attributes, where it does not,
|
|
609
|
+
and reads either. Every place a workbook keeps text goes through
|
|
610
|
+
`_xstring`, because one that does not either writes a file Excel refuses or
|
|
611
|
+
reads back seven characters for one. A table's headers are the exception:
|
|
612
|
+
Excel replaces such a character there with U+FFFD rather than escaping it.
|
|
613
|
+
|
|
614
|
+
**Rich text is read by rules its markup does not state.** Excel writes the
|
|
615
|
+
first run of a cell's text with no font and gives the cell that run's font;
|
|
616
|
+
every later run carries its font in full. Other writers leave runs less
|
|
617
|
+
complete, and Excel reads them by rules measured here rather than found in
|
|
618
|
+
the specification. Runs with no font show in the cell's font until a run has
|
|
619
|
+
one, and in the workbook's default font after that. A run's font takes what
|
|
620
|
+
it leaves unsaid from the default font as well, so a run that says only
|
|
621
|
+
"bold" in a Courier cell shows in the default typeface, not in Courier.
|
|
476
622
|
|
|
477
623
|
**Inserting a row is not a local edit.** A cell's address is written into the
|
|
478
624
|
file in a dozen places, and missing one gives a workbook that opens cleanly
|
|
@@ -590,6 +736,28 @@ Note that `ActiveWindow.SplitRow` and `SplitColumn` are *not* how to verify a
|
|
|
590
736
|
freeze: a workbook Excel froze itself reports 0 for both, so they discriminate
|
|
591
737
|
nothing. `Panes.Count` and where `VisibleRange` starts do.
|
|
592
738
|
|
|
739
|
+
**A formula's value is Excel's arithmetic, not IEEE's.** The engine in
|
|
740
|
+
`excel/_calc` is held to `formulas.xlsx`, where each of 10,958 formulas sits
|
|
741
|
+
beside the value Excel calculated for it, and what that corpus pinned down
|
|
742
|
+
is written into the modules that follow it. Equality reads both sides to
|
|
743
|
+
fifteen digits. A formula's last `+` or `-`, and SUM's last addition, give
|
|
744
|
+
0 under eight units in the last place of the left operand, which the
|
|
745
|
+
corpus found by measuring each pair of numbers both ways round. A number
|
|
746
|
+
literal or a number read from text keeps fifteen digits, cut. Arithmetic
|
|
747
|
+
reproduces the x87 unit Excel computes in (`precise.py`): each operation
|
|
748
|
+
rounds to 64 bits and then to 53, so `8^(-1/3)` is exactly 0.5 though
|
|
749
|
+
`1/1.9999999999999998` is not, EXP and LN follow F2XM1 and FYL2X, and SIN,
|
|
750
|
+
COS and TAN reduce with pi to 64 bits. Sums run in order through
|
|
751
|
+
`precise.summed`, never Python's `sum`, which compensates from 3.12 on.
|
|
752
|
+
Where the corpus narrowed a function to a few units in the last place
|
|
753
|
+
without fixing it, the test says so by name.
|
|
754
|
+
|
|
755
|
+
The engine calculates without recursion. Reading a cell whose formula is
|
|
756
|
+
not calculated raises `PendingCellsError` naming every such cell the read
|
|
757
|
+
touched; those go on an explicit stack and the reader is tried again.
|
|
758
|
+
A range read names all its uncalculated cells at once, so a SUM over a
|
|
759
|
+
column of formulas retries once, not once per cell.
|
|
760
|
+
|
|
593
761
|
### 8.3 The live gate
|
|
594
762
|
|
|
595
763
|
`test_excel_live_gate.py` is the only check that can prove Excel accepts what
|