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.
Files changed (84) hide show
  1. {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/PKG-INFO +66 -11
  2. {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/README.md +65 -10
  3. {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/docs/architecture.md +176 -8
  4. pyofficeeditor-0.3.0/docs/changelog.md +797 -0
  5. {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/pyproject.toml +1 -1
  6. {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/_xml.py +41 -4
  7. {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/__init__.py +61 -0
  8. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/__init__.py +17 -0
  9. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/catalog.py +72 -0
  10. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/dates.py +214 -0
  11. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/engine.py +549 -0
  12. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/evaluator.py +797 -0
  13. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/functions/__init__.py +47 -0
  14. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/functions/aggregate.py +370 -0
  15. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/functions/arithmetic.py +756 -0
  16. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/functions/arrays.py +451 -0
  17. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/functions/common.py +277 -0
  18. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/functions/conditional.py +137 -0
  19. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/functions/database.py +266 -0
  20. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/functions/dates.py +442 -0
  21. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/functions/distributions.py +1270 -0
  22. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/functions/engineering.py +929 -0
  23. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/functions/financial.py +1555 -0
  24. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/functions/information.py +247 -0
  25. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/functions/lambdas.py +175 -0
  26. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/functions/logical.py +154 -0
  27. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/functions/lookup.py +427 -0
  28. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/functions/matrix.py +130 -0
  29. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/functions/statistics.py +727 -0
  30. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/functions/subtotal.py +243 -0
  31. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/functions/text.py +642 -0
  32. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/lexer.py +479 -0
  33. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/nodes.py +419 -0
  34. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/numbers.py +199 -0
  35. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/parser.py +245 -0
  36. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/precise.py +431 -0
  37. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/registry.py +157 -0
  38. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/special.py +521 -0
  39. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_calc/values.py +402 -0
  40. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_cellstyles.py +256 -0
  41. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_chartbuild.py +384 -0
  42. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_charts.py +267 -0
  43. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_collate.py +200 -0
  44. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_comments.py +626 -0
  45. {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/_conditional.py +9 -4
  46. {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/_dimensions.py +92 -6
  47. {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/_dxf.py +7 -2
  48. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_filters.py +1072 -0
  49. {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/_formats.py +3 -1
  50. {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/_hyperlinks.py +11 -6
  51. {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/_names.py +16 -8
  52. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_numfmt.py +1351 -0
  53. {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/_pagesetup.py +3 -2
  54. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_pictures.py +170 -0
  55. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_pivots.py +137 -0
  56. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_richtext.py +169 -0
  57. {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/_rowcol.py +342 -16
  58. {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/_schema.py +11 -0
  59. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_shapes.py +1541 -0
  60. {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/_sharedstrings.py +41 -15
  61. {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/_styles.py +202 -14
  62. {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/_tables.py +44 -17
  63. {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/_tokens.py +5 -2
  64. {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/_validation.py +14 -7
  65. {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/_values.py +58 -14
  66. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/_xstring.py +87 -0
  67. {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/workbook.py +357 -34
  68. pyofficeeditor-0.3.0/src/pyofficeeditor/excel/worksheet.py +4195 -0
  69. {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/exceptions.py +6 -0
  70. {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/opc.py +6 -2
  71. pyofficeeditor-0.3.0/tests/fixtures/excel/README.md +117 -0
  72. pyofficeeditor-0.2.2/docs/changelog.md +0 -213
  73. pyofficeeditor-0.2.2/src/pyofficeeditor/excel/_shapes.py +0 -553
  74. pyofficeeditor-0.2.2/src/pyofficeeditor/excel/worksheet.py +0 -2246
  75. pyofficeeditor-0.2.2/tests/fixtures/excel/README.md +0 -72
  76. {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/.gitignore +0 -0
  77. {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/LICENSE.md +0 -0
  78. {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/__init__.py +0 -0
  79. {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/_zip.py +0 -0
  80. {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/_addresses.py +0 -0
  81. {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/_formulas.py +0 -0
  82. {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/_protection.py +0 -0
  83. {pyofficeeditor-0.2.2 → pyofficeeditor-0.3.0}/src/pyofficeeditor/excel/_reference.py +0 -0
  84. {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.2.2
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 Border, Dxf, Workbook, cell_is, gradient
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 tables share |
171
- | _shapes shapes, and the grid that places a control |
172
- | _pagesetup margins in inches, and the three header |
173
- | boxes coded into one string |
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 Border, Dxf, Workbook, cell_is, gradient
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 tables share |
134
- | _shapes shapes, and the grid that places a control |
135
- | _pagesetup margins in inches, and the three header |
136
- | boxes coded into one string |
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/ three committed Excel-authored packages,
307
- plus two built on demand; see its README
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 six committed
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 dimension needs its companion flag.** A `width` without `customWidth="1"`
474
- and an `ht` without `customHeight="1"` are ignored, so the value looks like it
475
- never took.
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