pyOpenVBA 5.2.4__tar.gz → 6.0.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.
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/.gitignore +3 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/PKG-INFO +111 -1
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/README.md +110 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/docs/roadmap.md +23 -1
- pyopenvba-6.0.0/docs/vba_runtime.md +374 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/pyproject.toml +2 -1
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/__init__.py +1 -1
- pyopenvba-6.0.0/src/pyopenvba/_a1.py +205 -0
- pyopenvba-6.0.0/src/pyopenvba/_xml.py +70 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_sql.py +4 -1
- pyopenvba-6.0.0/src/pyopenvba/apps/__init__.py +6 -0
- pyopenvba-6.0.0/src/pyopenvba/apps/excel/__init__.py +292 -0
- pyopenvba-6.0.0/src/pyopenvba/apps/excel/_bridge.py +75 -0
- pyopenvba-6.0.0/src/pyopenvba/apps/excel/_calc.py +310 -0
- pyopenvba-6.0.0/src/pyopenvba/apps/excel/_io.py +1157 -0
- pyopenvba-6.0.0/src/pyopenvba/apps/excel/_model.py +2047 -0
- pyopenvba-6.0.0/src/pyopenvba/apps/excel/_refresh.py +264 -0
- pyopenvba-6.0.0/src/pyopenvba/apps/excel/_shapes.py +481 -0
- pyopenvba-6.0.0/src/pyopenvba/apps/powerpoint/__init__.py +220 -0
- pyopenvba-6.0.0/src/pyopenvba/apps/powerpoint/_bridge.py +94 -0
- pyopenvba-6.0.0/src/pyopenvba/apps/powerpoint/_io.py +118 -0
- pyopenvba-6.0.0/src/pyopenvba/apps/powerpoint/_model.py +790 -0
- pyopenvba-6.0.0/src/pyopenvba/apps/word/__init__.py +179 -0
- pyopenvba-6.0.0/src/pyopenvba/apps/word/_bridge.py +103 -0
- pyopenvba-6.0.0/src/pyopenvba/apps/word/_io.py +80 -0
- pyopenvba-6.0.0/src/pyopenvba/apps/word/_model.py +646 -0
- pyopenvba-6.0.0/src/pyopenvba/exceptions.py +92 -0
- pyopenvba-6.0.0/src/pyopenvba/formula/__init__.py +59 -0
- pyopenvba-6.0.0/src/pyopenvba/formula/_engine.py +287 -0
- pyopenvba-6.0.0/src/pyopenvba/formula/_functions.py +1455 -0
- pyopenvba-6.0.0/src/pyopenvba/formula/_inventory.py +54 -0
- pyopenvba-6.0.0/src/pyopenvba/formula/_parse.py +444 -0
- pyopenvba-6.0.0/src/pyopenvba/formula/_values.py +334 -0
- pyopenvba-6.0.0/src/pyopenvba/interpreter/__init__.py +89 -0
- pyopenvba-6.0.0/src/pyopenvba/interpreter/_ast.py +439 -0
- pyopenvba-6.0.0/src/pyopenvba/interpreter/_constants_data.py +5330 -0
- pyopenvba-6.0.0/src/pyopenvba/interpreter/_intrinsics.py +1261 -0
- pyopenvba-6.0.0/src/pyopenvba/interpreter/_inventory.py +46 -0
- pyopenvba-6.0.0/src/pyopenvba/interpreter/_inventory_data.py +660 -0
- pyopenvba-6.0.0/src/pyopenvba/interpreter/_lex.py +304 -0
- pyopenvba-6.0.0/src/pyopenvba/interpreter/_objects.py +312 -0
- pyopenvba-6.0.0/src/pyopenvba/interpreter/_parse.py +1274 -0
- pyopenvba-6.0.0/src/pyopenvba/interpreter/_preprocess.py +246 -0
- pyopenvba-6.0.0/src/pyopenvba/interpreter/_runtime.py +1871 -0
- pyopenvba-6.0.0/src/pyopenvba/interpreter/_values.py +1116 -0
- pyopenvba-6.0.0/src/pyopenvba/mlang/__init__.py +59 -0
- pyopenvba-6.0.0/src/pyopenvba/mlang/_eval.py +530 -0
- pyopenvba-6.0.0/src/pyopenvba/mlang/_inventory.py +26 -0
- pyopenvba-6.0.0/src/pyopenvba/mlang/_inventory_data.py +193 -0
- pyopenvba-6.0.0/src/pyopenvba/mlang/_library.py +2099 -0
- pyopenvba-6.0.0/src/pyopenvba/mlang/_parse.py +627 -0
- pyopenvba-6.0.0/src/pyopenvba/mlang/_values.py +397 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/powerquery/_sheets.py +3 -37
- pyopenvba-6.0.0/src/pyopenvba/shapes/__init__.py +34 -0
- pyopenvba-6.0.0/src/pyopenvba/shapes/_docx.py +404 -0
- pyopenvba-6.0.0/src/pyopenvba/shapes/_docx_text.py +126 -0
- pyopenvba-6.0.0/src/pyopenvba/shapes/_drawingml.py +195 -0
- pyopenvba-6.0.0/src/pyopenvba/shapes/_pptx.py +248 -0
- pyopenvba-6.0.0/src/pyopenvba/shapes/_values.py +218 -0
- pyopenvba-6.0.0/src/pyopenvba/shapes/_xlsx.py +737 -0
- pyopenvba-5.2.4/src/pyopenvba/exceptions.py +0 -35
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/LICENSE.md +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/docs/architecture.md +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/docs/ms-ovba-implementation-guide_v2.md +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/docs/power_query.md +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/docs/research/access_complex/README.md +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/docs/research/access_designs/README.md +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/docs/research/access_macros/README.md +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/docs/research/access_write/README.md +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/docs/research/pcode/README.md +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/__main__.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_deflate.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_host.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_oforms_pages.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_oforms_records.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_ppt_container.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/__init__.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/blank_files/blank_database.accdb +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/blank_files/blank_database_module.accdb +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/blank_files/blank_document.docm +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/blank_files/blank_excel_addin.xlam +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/blank_files/blank_presentation.pptm +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/blank_files/blank_workbook.xlsb +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/blank_files/blank_workbook.xlsm +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/blank_files/blank_workbook.xlsx +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/blank_files/engine_skeleton.accdb +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/blank_files/engine_skeleton.mdb +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/designs/form.blob +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/designs/form.lvprop +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/designs/form.propdata +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/designs/form.prototypes +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/designs/form.typeinfo +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/designs/report.blob +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/designs/report.lvprop +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/designs/report.propdata +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/designs/report.prototypes +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/designs/report.typeinfo +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/__init__.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_alloc.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_btree.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_collation.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_collation_general_legacy.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_compact.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_complex.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_datapage.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_ddl.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_designs.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_facade.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_format.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_index.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_lval.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_macros.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_pages.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_props.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_queries.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_rows.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_schema.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_storage.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_tdef.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_validate.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_vba.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/database.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access_read.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/cfb.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/excel.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/forms.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/powerpoint.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/powerquery/__init__.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/powerquery/_binary.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/powerquery/_files.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/powerquery/_mashup.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/powerquery/_metadata.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/powerquery/_opc.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/powerquery/_package.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/powerquery/_refresh.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/powerquery/_section.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/powerquery/workbook.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/vba.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/vba_pcode.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/word.py +0 -0
- {pyopenvba-5.2.4 → pyopenvba-6.0.0}/tests/fuzz_corpus/README.md +0 -0
|
@@ -36,6 +36,9 @@ Thumbs.db
|
|
|
36
36
|
tests/fixtures/*.xlsm
|
|
37
37
|
tests/fixtures/*.xls
|
|
38
38
|
|
|
39
|
+
# Workbooks the measuring scripts build to ask live Office a question
|
|
40
|
+
tests/fixtures/mlang/_scratch/
|
|
41
|
+
|
|
39
42
|
# Manually-generated Excel verification set (rebuilt by
|
|
40
43
|
# scripts/build_excel_verification_set.py)
|
|
41
44
|
tests/live_excel_testing/_excel_verify/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pyOpenVBA
|
|
3
|
-
Version:
|
|
3
|
+
Version: 6.0.0
|
|
4
4
|
Summary: Read and write VBA macros and Excel Power Query in pure Python, no dependencies.
|
|
5
5
|
Project-URL: Homepage, https://github.com/WilliamSmithEdward/pyOpenVBA
|
|
6
6
|
Project-URL: Repository, https://github.com/WilliamSmithEdward/pyOpenVBA
|
|
@@ -504,6 +504,116 @@ What the format is, and how each rule was measured, is in
|
|
|
504
504
|
|
|
505
505
|
---
|
|
506
506
|
|
|
507
|
+
## Running the macros
|
|
508
|
+
|
|
509
|
+
Reading and writing VBA is one thing; this runs it. `ExcelApplication`
|
|
510
|
+
is an Excel instance in memory, with a VBA interpreter attached: load a
|
|
511
|
+
workbook, execute a macro, look at what it did, write the file back.
|
|
512
|
+
|
|
513
|
+
```python
|
|
514
|
+
from pyopenvba.apps.excel import ExcelApplication
|
|
515
|
+
|
|
516
|
+
app = ExcelApplication.open("report.xlsm") # sheets, names, queries, macros
|
|
517
|
+
app.run("BuildReport")
|
|
518
|
+
print(app.describe()) # a readable dump of the grid
|
|
519
|
+
app.save("report_out.xlsm")
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
State can also be made up from nothing, with no file at all:
|
|
523
|
+
|
|
524
|
+
```python
|
|
525
|
+
app = ExcelApplication()
|
|
526
|
+
app.add_workbook()
|
|
527
|
+
app.add_module('Sub Main()\n Range("A1").Value = 42\nEnd Sub\n', name="Module1")
|
|
528
|
+
app.run("Main")
|
|
529
|
+
app.sheet(1).value("A1") # 42
|
|
530
|
+
app.evaluate('Range("A1").Value * 2') # 84
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
Failures are three separate exceptions, because they mean different
|
|
534
|
+
things: `VBACompileError` for VBA that does not compile,
|
|
535
|
+
`VBARuntimeError` for an error VBA itself would raise, carrying the
|
|
536
|
+
number `Err` would hold, and `VBAUnsupportedError` for real VBA that
|
|
537
|
+
pyOpenVBA does not implement. The third is never trappable by
|
|
538
|
+
`On Error`: a gap here must not be swallowed and reported as a result.
|
|
539
|
+
|
|
540
|
+
Formulas are calculated, not just carried: write one from a macro and
|
|
541
|
+
read the answer back.
|
|
542
|
+
|
|
543
|
+
```python
|
|
544
|
+
app.add_module('''
|
|
545
|
+
Sub Total()
|
|
546
|
+
Range("A1:A3").Value = 10
|
|
547
|
+
Range("B1").Formula = "=SUM(A1:A3)"
|
|
548
|
+
End Sub
|
|
549
|
+
''', name="Module1")
|
|
550
|
+
app.run("Total")
|
|
551
|
+
app.sheet(1).value("B1") # 30.0
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
About a hundred worksheet functions are implemented; anything else
|
|
555
|
+
Excel has says so by name rather than quietly answering `#NAME?`.
|
|
556
|
+
|
|
557
|
+
Power Query is evaluated too. Refreshing a query works out its M and
|
|
558
|
+
writes the rows to the sheet it loads to, so the table, its queryTable
|
|
559
|
+
and the saved file all follow:
|
|
560
|
+
|
|
561
|
+
```python
|
|
562
|
+
app = ExcelApplication.open("sales.xlsx")
|
|
563
|
+
app.refresh_query("Summary") # the rows, headers first
|
|
564
|
+
app.save("sales_out.xlsx")
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
Queries can name each other, `Excel.CurrentWorkbook()` reads the
|
|
568
|
+
workbook's own tables and named ranges, and a source this cannot reach
|
|
569
|
+
without a network or a driver reports itself rather than guessing.
|
|
570
|
+
|
|
571
|
+
Word and PowerPoint run their macros too, each with its own object
|
|
572
|
+
model:
|
|
573
|
+
|
|
574
|
+
```python
|
|
575
|
+
from pyopenvba.apps.word import WordApplication
|
|
576
|
+
from pyopenvba.apps.powerpoint import PowerPointApplication
|
|
577
|
+
|
|
578
|
+
doc = WordApplication.open("report.docm")
|
|
579
|
+
doc.run("Relabel")
|
|
580
|
+
doc.save("report_out.docm")
|
|
581
|
+
|
|
582
|
+
deck = PowerPointApplication.open("deck.pptm")
|
|
583
|
+
deck.run("Rebuild")
|
|
584
|
+
deck.slide(1).shapes() # what is on the first slide
|
|
585
|
+
deck.save("deck_out.pptm")
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
Shapes are read and written on all three surfaces: adding, moving,
|
|
589
|
+
resizing, renaming, retyping and deleting, with the macro a click runs
|
|
590
|
+
attached or removed. In Excel that is `Shape.OnAction`, form-control
|
|
591
|
+
buttons included; in PowerPoint it is
|
|
592
|
+
`ActionSettings(ppMouseClick).Run`; Word has no macro on a shape and
|
|
593
|
+
says so rather than pretending.
|
|
594
|
+
|
|
595
|
+
```python
|
|
596
|
+
app.add_module('''
|
|
597
|
+
Sub Draw()
|
|
598
|
+
Dim sh As Object
|
|
599
|
+
Set sh = ActiveSheet.Shapes.AddFormControl(0, 100, 50, 90, 30)
|
|
600
|
+
sh.Name = "Go"
|
|
601
|
+
sh.OnAction = "Refresh"
|
|
602
|
+
sh.TextFrame.Characters.Text = "Refresh"
|
|
603
|
+
End Sub
|
|
604
|
+
''', name="Module1")
|
|
605
|
+
app.run("Draw")
|
|
606
|
+
app.save("with_button.xlsm") # Excel opens it as a button
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
Every answer the interpreter, the calculation engine and the three
|
|
610
|
+
object models give was measured in the application itself rather than
|
|
611
|
+
assumed, and a file nobody changed saves back byte for byte. What is
|
|
612
|
+
implemented, what is not, and how it was measured is in
|
|
613
|
+
[docs/vba_runtime.md](https://github.com/WilliamSmithEdward/pyOpenVBA/blob/main/docs/vba_runtime.md).
|
|
614
|
+
|
|
615
|
+
---
|
|
616
|
+
|
|
507
617
|
## Supported formats
|
|
508
618
|
|
|
509
619
|
### Excel
|
|
@@ -467,6 +467,116 @@ What the format is, and how each rule was measured, is in
|
|
|
467
467
|
|
|
468
468
|
---
|
|
469
469
|
|
|
470
|
+
## Running the macros
|
|
471
|
+
|
|
472
|
+
Reading and writing VBA is one thing; this runs it. `ExcelApplication`
|
|
473
|
+
is an Excel instance in memory, with a VBA interpreter attached: load a
|
|
474
|
+
workbook, execute a macro, look at what it did, write the file back.
|
|
475
|
+
|
|
476
|
+
```python
|
|
477
|
+
from pyopenvba.apps.excel import ExcelApplication
|
|
478
|
+
|
|
479
|
+
app = ExcelApplication.open("report.xlsm") # sheets, names, queries, macros
|
|
480
|
+
app.run("BuildReport")
|
|
481
|
+
print(app.describe()) # a readable dump of the grid
|
|
482
|
+
app.save("report_out.xlsm")
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
State can also be made up from nothing, with no file at all:
|
|
486
|
+
|
|
487
|
+
```python
|
|
488
|
+
app = ExcelApplication()
|
|
489
|
+
app.add_workbook()
|
|
490
|
+
app.add_module('Sub Main()\n Range("A1").Value = 42\nEnd Sub\n', name="Module1")
|
|
491
|
+
app.run("Main")
|
|
492
|
+
app.sheet(1).value("A1") # 42
|
|
493
|
+
app.evaluate('Range("A1").Value * 2') # 84
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
Failures are three separate exceptions, because they mean different
|
|
497
|
+
things: `VBACompileError` for VBA that does not compile,
|
|
498
|
+
`VBARuntimeError` for an error VBA itself would raise, carrying the
|
|
499
|
+
number `Err` would hold, and `VBAUnsupportedError` for real VBA that
|
|
500
|
+
pyOpenVBA does not implement. The third is never trappable by
|
|
501
|
+
`On Error`: a gap here must not be swallowed and reported as a result.
|
|
502
|
+
|
|
503
|
+
Formulas are calculated, not just carried: write one from a macro and
|
|
504
|
+
read the answer back.
|
|
505
|
+
|
|
506
|
+
```python
|
|
507
|
+
app.add_module('''
|
|
508
|
+
Sub Total()
|
|
509
|
+
Range("A1:A3").Value = 10
|
|
510
|
+
Range("B1").Formula = "=SUM(A1:A3)"
|
|
511
|
+
End Sub
|
|
512
|
+
''', name="Module1")
|
|
513
|
+
app.run("Total")
|
|
514
|
+
app.sheet(1).value("B1") # 30.0
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
About a hundred worksheet functions are implemented; anything else
|
|
518
|
+
Excel has says so by name rather than quietly answering `#NAME?`.
|
|
519
|
+
|
|
520
|
+
Power Query is evaluated too. Refreshing a query works out its M and
|
|
521
|
+
writes the rows to the sheet it loads to, so the table, its queryTable
|
|
522
|
+
and the saved file all follow:
|
|
523
|
+
|
|
524
|
+
```python
|
|
525
|
+
app = ExcelApplication.open("sales.xlsx")
|
|
526
|
+
app.refresh_query("Summary") # the rows, headers first
|
|
527
|
+
app.save("sales_out.xlsx")
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
Queries can name each other, `Excel.CurrentWorkbook()` reads the
|
|
531
|
+
workbook's own tables and named ranges, and a source this cannot reach
|
|
532
|
+
without a network or a driver reports itself rather than guessing.
|
|
533
|
+
|
|
534
|
+
Word and PowerPoint run their macros too, each with its own object
|
|
535
|
+
model:
|
|
536
|
+
|
|
537
|
+
```python
|
|
538
|
+
from pyopenvba.apps.word import WordApplication
|
|
539
|
+
from pyopenvba.apps.powerpoint import PowerPointApplication
|
|
540
|
+
|
|
541
|
+
doc = WordApplication.open("report.docm")
|
|
542
|
+
doc.run("Relabel")
|
|
543
|
+
doc.save("report_out.docm")
|
|
544
|
+
|
|
545
|
+
deck = PowerPointApplication.open("deck.pptm")
|
|
546
|
+
deck.run("Rebuild")
|
|
547
|
+
deck.slide(1).shapes() # what is on the first slide
|
|
548
|
+
deck.save("deck_out.pptm")
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
Shapes are read and written on all three surfaces: adding, moving,
|
|
552
|
+
resizing, renaming, retyping and deleting, with the macro a click runs
|
|
553
|
+
attached or removed. In Excel that is `Shape.OnAction`, form-control
|
|
554
|
+
buttons included; in PowerPoint it is
|
|
555
|
+
`ActionSettings(ppMouseClick).Run`; Word has no macro on a shape and
|
|
556
|
+
says so rather than pretending.
|
|
557
|
+
|
|
558
|
+
```python
|
|
559
|
+
app.add_module('''
|
|
560
|
+
Sub Draw()
|
|
561
|
+
Dim sh As Object
|
|
562
|
+
Set sh = ActiveSheet.Shapes.AddFormControl(0, 100, 50, 90, 30)
|
|
563
|
+
sh.Name = "Go"
|
|
564
|
+
sh.OnAction = "Refresh"
|
|
565
|
+
sh.TextFrame.Characters.Text = "Refresh"
|
|
566
|
+
End Sub
|
|
567
|
+
''', name="Module1")
|
|
568
|
+
app.run("Draw")
|
|
569
|
+
app.save("with_button.xlsm") # Excel opens it as a button
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
Every answer the interpreter, the calculation engine and the three
|
|
573
|
+
object models give was measured in the application itself rather than
|
|
574
|
+
assumed, and a file nobody changed saves back byte for byte. What is
|
|
575
|
+
implemented, what is not, and how it was measured is in
|
|
576
|
+
[docs/vba_runtime.md](https://github.com/WilliamSmithEdward/pyOpenVBA/blob/main/docs/vba_runtime.md).
|
|
577
|
+
|
|
578
|
+
---
|
|
579
|
+
|
|
470
580
|
## Supported formats
|
|
471
581
|
|
|
472
582
|
### Excel
|
|
@@ -42,6 +42,12 @@ done and what is not.
|
|
|
42
42
|
`form.add_control()`, `form.add_page()`, `control.set_property()`,
|
|
43
43
|
`python -m pyopenvba forms`). Writing is lossless: an unedited form
|
|
44
44
|
saves back byte for byte.
|
|
45
|
+
- **Running the VBA**: a VBA interpreter and an in-memory Excel object
|
|
46
|
+
model (`pyopenvba.apps.excel.ExcelApplication`), so a macro can be
|
|
47
|
+
executed against a workbook loaded from a file or made up from
|
|
48
|
+
nothing, and the result written back out. Every expression answer is
|
|
49
|
+
measured against live Excel; see
|
|
50
|
+
[`vba_runtime.md`](vba_runtime.md).
|
|
45
51
|
- Pure Python 3.10+, zero runtime dependencies.
|
|
46
52
|
|
|
47
53
|
### Unsupported (today)
|
|
@@ -85,7 +91,23 @@ done and what is not.
|
|
|
85
91
|
|
|
86
92
|
## Near-term roadmap (in priority order)
|
|
87
93
|
|
|
88
|
-
|
|
94
|
+
The MS-OVBA gates are all PASS. What is open belongs to the newer
|
|
95
|
+
interpreter work, tracked in [`vba_runtime.md`](vba_runtime.md):
|
|
96
|
+
|
|
97
|
+
1. An Access object model with its own measured probe file, following
|
|
98
|
+
the shape `apps/excel`, `apps/word` and `apps/powerpoint` set.
|
|
99
|
+
2. More of the M library, which is 224 of the 859 names `#shared`
|
|
100
|
+
reports. The gaps are named rather than answered wrongly, so what to
|
|
101
|
+
add next is whatever a real query asks for.
|
|
102
|
+
3. `WithEvents` and the document-module event handlers, so a
|
|
103
|
+
`Worksheet_Change` fires when a macro writes a cell.
|
|
104
|
+
4. Array formulas that spill, which the calculation engine stops short
|
|
105
|
+
of: an array result shows its first element, as it did before
|
|
106
|
+
dynamic arrays.
|
|
107
|
+
5. The shapes this reads but does not make: a group, a picture and a
|
|
108
|
+
chart. All three are read, and their members and parts are reachable;
|
|
109
|
+
`ShapeRange.Group` and `AddPicture` report themselves rather than
|
|
110
|
+
writing a part they cannot yet write.
|
|
89
111
|
|
|
90
112
|
## Out of scope (no current plans)
|
|
91
113
|
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
# Running VBA against an in-memory Office application
|
|
2
|
+
|
|
3
|
+
pyOpenVBA reads and writes the VBA in an Office file. This part runs
|
|
4
|
+
it: a VBA interpreter, and an object model with real state behind it, so
|
|
5
|
+
a macro can be executed and its effect inspected without Office.
|
|
6
|
+
|
|
7
|
+
Three hosts: `ExcelApplication`, `WordApplication` and
|
|
8
|
+
`PowerPointApplication`, each with its own object model and its own
|
|
9
|
+
measured probe file.
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
from pyopenvba.apps.excel import ExcelApplication
|
|
13
|
+
|
|
14
|
+
app = ExcelApplication.open("report.xlsm") # sheets, names, queries, macros
|
|
15
|
+
app.run("BuildReport") # evaluate the VBA
|
|
16
|
+
print(app.describe()) # what is in there now
|
|
17
|
+
app.save("report_out.xlsm") # write it back
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
or with nothing to start from:
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
app = ExcelApplication()
|
|
24
|
+
app.add_workbook()
|
|
25
|
+
app.add_module('Sub Main()\n Range("A1").Value = 42\nEnd Sub\n', name="Module1")
|
|
26
|
+
app.run("Main")
|
|
27
|
+
app.sheet(1).value("A1") # 42
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Word and PowerPoint work the same way:
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from pyopenvba.apps.word import WordApplication
|
|
34
|
+
|
|
35
|
+
app = WordApplication.open("report.docm")
|
|
36
|
+
app.run("Relabel")
|
|
37
|
+
print(app.describe())
|
|
38
|
+
app.save("report_out.docm")
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from pyopenvba.apps.powerpoint import PowerPointApplication
|
|
43
|
+
|
|
44
|
+
app = PowerPointApplication.open("deck.pptm")
|
|
45
|
+
app.run("Rebuild")
|
|
46
|
+
app.slide(1).shapes() # what is on the first slide
|
|
47
|
+
app.save("deck_out.pptm")
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## The three ways it can fail
|
|
51
|
+
|
|
52
|
+
They are separate exception types because they mean different things to
|
|
53
|
+
whoever is reading the output.
|
|
54
|
+
|
|
55
|
+
| Exception | Means | Trappable by `On Error`? |
|
|
56
|
+
| --- | --- | --- |
|
|
57
|
+
| `VBACompileError` | The source is not valid VBA. Raised when the module is loaded, as the VBA compiler does. | No, as in VBA |
|
|
58
|
+
| `VBARuntimeError` | An error VBA itself would raise, carrying the number and description `Err` would hold. | Yes |
|
|
59
|
+
| `VBAUnsupportedError` | Real VBA that pyOpenVBA does not implement. | **No, deliberately** |
|
|
60
|
+
|
|
61
|
+
The third one is the point of the separation. A statement, a function
|
|
62
|
+
or an object member that Excel would have run and this cannot is a gap
|
|
63
|
+
in pyOpenVBA, not a mistake by whoever wrote the macro, and reporting it
|
|
64
|
+
as either of the other two would say otherwise.
|
|
65
|
+
|
|
66
|
+
It is not trappable on purpose. If `On Error Resume Next` could swallow
|
|
67
|
+
it, a script would carry on past a step that never happened and report a
|
|
68
|
+
result computed from a state that never existed.
|
|
69
|
+
|
|
70
|
+
An unknown object member is told apart by the type library rather than
|
|
71
|
+
guessed: `Worksheet.PivotTables` is a real member, so reaching for it is
|
|
72
|
+
`VBAUnsupportedError`; `Worksheet.Pivottabel` is not, so it is VBA's own
|
|
73
|
+
run-time error 438. The inventory is generated by
|
|
74
|
+
`scripts/build_object_inventory.py` and committed as
|
|
75
|
+
`interpreter/_inventory_data.py`.
|
|
76
|
+
|
|
77
|
+
## How the semantics were settled
|
|
78
|
+
|
|
79
|
+
Every answer the interpreter gives to an expression was measured in live
|
|
80
|
+
Excel rather than reasoned about. `tests/fixtures/vba_semantics/probes.txt`
|
|
81
|
+
holds the expressions; `scripts/measure_vba_semantics.py` runs them
|
|
82
|
+
through Excel via pyvbaharness and records what came back;
|
|
83
|
+
`tests/test_vba_semantics.py` holds the interpreter to that file, with no
|
|
84
|
+
Office needed.
|
|
85
|
+
|
|
86
|
+
That is how the model ended up with things a plausible implementation
|
|
87
|
+
would have got wrong:
|
|
88
|
+
|
|
89
|
+
- `30000 + 30000` overflows, because both operands are Integers and VBA
|
|
90
|
+
does not widen the result. Integer width is carried on every value.
|
|
91
|
+
- `&HFFFF` is -1 and `&HFFFFFFFF` is -1, but the first is an Integer and
|
|
92
|
+
the second a Long: the width comes from how many digits were written.
|
|
93
|
+
- `1 < "1"` is False. A number against a string reads the string as a
|
|
94
|
+
number rather than sorting numbers before strings.
|
|
95
|
+
- `Len(n)` is 4 for a Long holding 12345 and 5 for a Variant holding the
|
|
96
|
+
same number: `Len` is a size in bytes for anything that is not a
|
|
97
|
+
String.
|
|
98
|
+
- A Double prints in full while its digits fit in 15 character
|
|
99
|
+
positions and in exponent form otherwise, so `999999999999999` is
|
|
100
|
+
written out and `1E+15` is not, and `0.000000000000001` is written out
|
|
101
|
+
and `1E-16` is not.
|
|
102
|
+
- `Right("abc", 5)` is `"abc"`, `Hex(-1)` is `FFFF` for an Integer, and
|
|
103
|
+
`Round(-0.5)` prints `-0`.
|
|
104
|
+
|
|
105
|
+
The calculation engine was settled the same way, in
|
|
106
|
+
`tests/fixtures/formula/probes.txt`: each formula was written into a
|
|
107
|
+
cell in live Excel and what the cell showed was recorded. That sweep
|
|
108
|
+
is what settled these:
|
|
109
|
+
|
|
110
|
+
- `2^3^2` is 64, because `^` chains to the left, and `-2^2` is 4,
|
|
111
|
+
because a minus sign binds tighter than the power.
|
|
112
|
+
- `0.1+0.2=0.3` is TRUE: a comparison is settled on the fifteen
|
|
113
|
+
significant digits a cell would show, not on the bits.
|
|
114
|
+
- ROUND rounds half away from zero and MOD takes the sign of its
|
|
115
|
+
divisor, neither of which matches the VBA function of the same name.
|
|
116
|
+
- A range contributes only its numbers to SUM while a literal converts,
|
|
117
|
+
so `SUM(A1,"2")` is three and a range holding "2" adds nothing; COUNT
|
|
118
|
+
walks past an error that SUM would stop at.
|
|
119
|
+
- `=A1` on an empty cell is 0, and `DATE(...)` brings a date format
|
|
120
|
+
with it while `EOMONTH(...)` leaves the serial number showing.
|
|
121
|
+
|
|
122
|
+
The same method covers the Excel object model:
|
|
123
|
+
`tests/fixtures/excel_model/probes.txt` and `tests/test_excel_model.py`,
|
|
124
|
+
measured with `python scripts/measure_vba_semantics.py excel`. That
|
|
125
|
+
sweep is what settled these:
|
|
126
|
+
|
|
127
|
+
- A cell keeps every number as a Double, and reads a string the way it
|
|
128
|
+
reads typing: `"5"` becomes the number, `"1/2/2020"` becomes a Date,
|
|
129
|
+
and `""` leaves the cell Empty.
|
|
130
|
+
- `Range("A1:B2").Value = Array(7, 8)` puts 7 in A2 as well as A1: a
|
|
131
|
+
flat array is one row, repeated down.
|
|
132
|
+
- `End` looks at the neighbour, so `End(xlDown)` from a lone A1 is
|
|
133
|
+
A1048576 rather than A1.
|
|
134
|
+
- `Range("ZZ")` is error 1004, because a column on its own needs the
|
|
135
|
+
colon.
|
|
136
|
+
|
|
137
|
+
Power Query is measured by the same method, through the engine that
|
|
138
|
+
ships with Excel: `scripts/measure_m.py` builds a workbook whose query
|
|
139
|
+
evaluates every expression in `tests/fixtures/mlang/probes.txt`,
|
|
140
|
+
refreshes it, and records what came back, 25 to a refresh. All 269 are
|
|
141
|
+
replayed by `tests/test_mlang_semantics.py`. That sweep settled these:
|
|
142
|
+
|
|
143
|
+
- `Number.Round(2.5)` is 2 and `Number.Round(3.5)` is 4: M rounds half
|
|
144
|
+
to even, where Excel's own ROUND rounds away from zero. It writes
|
|
145
|
+
the answer as `2.0`, keeping the decimal places the number arrived
|
|
146
|
+
with, so `Number.Round(2.345, 2)` writes `2.340`.
|
|
147
|
+
- `#date(2021, 3, 4) + #duration(0, 12, 0, 0)` is still a date, not a
|
|
148
|
+
datetime: the day the moment lands on.
|
|
149
|
+
- A duration writes its day count only when it has one, so three hours
|
|
150
|
+
is `03:00:00` and a day and two hours is `1.02:00:00`.
|
|
151
|
+
- `10/0` is `∞`, `0/0` is NaN and `Number.Sqrt(-1)` is NaN, none of
|
|
152
|
+
which is an error.
|
|
153
|
+
- A row whose test comes out null is dropped by `Table.SelectRows`, the
|
|
154
|
+
same as a row that tested false.
|
|
155
|
+
- `{"0".."9"}` is the ten digits: a range can be over characters.
|
|
156
|
+
- M has `Uri.EscapeDataString` and no `Uri.UnescapeDataString`, which is
|
|
157
|
+
why `#shared` is asked for the list of names rather than assuming the
|
|
158
|
+
library is symmetric.
|
|
159
|
+
|
|
160
|
+
Word and PowerPoint were measured the same way, into
|
|
161
|
+
`tests/fixtures/word_model` and `tests/fixtures/powerpoint_model`, with
|
|
162
|
+
`python scripts/measure_vba_semantics.py word` and `powerpoint`. Their
|
|
163
|
+
answers are not each other's, which is the reason for measuring each:
|
|
164
|
+
|
|
165
|
+
- A missing shape raises -2147024809 in Excel and Word, and
|
|
166
|
+
-2147188160 in PowerPoint. Neither is 1004.
|
|
167
|
+
- Word places a shape from the page and reports it against the column
|
|
168
|
+
and the paragraph, so `AddShape(1, 10, 20, 100, 50)` comes back with
|
|
169
|
+
a Left of -62 and a Top of -52 against the default inch margins.
|
|
170
|
+
- Word's names for its own shapes are not Excel's: a rounded rectangle
|
|
171
|
+
is "Rectangle: Rounded Corners", a can is a "Cylinder" and a text box
|
|
172
|
+
is "Text Box". PowerPoint's are Excel's, every one of them.
|
|
173
|
+
- A new shape's number comes from one counter per sheet, slide or
|
|
174
|
+
document, over every kind. A rectangle and then an oval are
|
|
175
|
+
"Rectangle 1" and "Oval 2"; delete both and the next rectangle is
|
|
176
|
+
"Rectangle 3"; a new sheet or slide starts again at one. The same
|
|
177
|
+
rule in all three hosts.
|
|
178
|
+
- The preset geometry behind an msoShapeType is not guessable from its
|
|
179
|
+
name, and several presets belong to a shape other than the one they
|
|
180
|
+
sound like: 11 is `plus`, 12 is `pentagon`, and `star5` is 92. The
|
|
181
|
+
whole table was read out of files Office saved, by
|
|
182
|
+
`python scripts/measure_shape_types.py`.
|
|
183
|
+
- `Document.Shapes` is listed by z-order, which rises as shapes are
|
|
184
|
+
added; the runs in the file hold the newest first.
|
|
185
|
+
- A Word range reads with its paragraph mark, an in-line shape is one
|
|
186
|
+
Chr(1) in the text, and a floating one is no character at all.
|
|
187
|
+
- Assigning `Content.Text` takes the shapes with it. Asked of Word, a
|
|
188
|
+
document with five floating shapes and one in-line had none of either
|
|
189
|
+
afterwards.
|
|
190
|
+
- PowerPoint's slide is 960 by 540 points, its `Presentation.Saved` is
|
|
191
|
+
a Long where Word's `Document.Saved` is a Boolean, and
|
|
192
|
+
`ActionSettings(1).Action` stays at ppActionRunMacro once set even
|
|
193
|
+
before a macro is named.
|
|
194
|
+
|
|
195
|
+
The shapes themselves were measured by having each application build
|
|
196
|
+
one of every kind it can hold and say what it then reported about each:
|
|
197
|
+
`python scripts/measure_shapes.py all`, kept in
|
|
198
|
+
`tests/fixtures/shapes`.
|
|
199
|
+
|
|
200
|
+
Three live gates ask what only Office can answer, behind
|
|
201
|
+
`RUN_LIVE_EXCEL=1`, `RUN_LIVE_WORD=1` and `RUN_LIVE_POWERPOINT=1`: that
|
|
202
|
+
the same macro leaves the same state behind on both sides, that a file
|
|
203
|
+
written here opens with everything the model does not describe still in
|
|
204
|
+
it, and that shapes drawn here are the shapes Office reads back.
|
|
205
|
+
|
|
206
|
+
## What is implemented
|
|
207
|
+
|
|
208
|
+
**The language.** Every statement form: the declarations, `If` in both
|
|
209
|
+
shapes, `For`, `For Each`, `Do` in all four, `While`, `Select Case`,
|
|
210
|
+
`With`, `Exit`, `GoTo` and labels, `On Error` with `Resume`, `ReDim`,
|
|
211
|
+
`Erase`, the `Mid` statement, `Type` and `Enum`, class modules with
|
|
212
|
+
`Property Get/Let/Set` and `Class_Initialize`, `ByRef` and `ByVal`,
|
|
213
|
+
`Optional` with defaults, `ParamArray`, named arguments, `Static`
|
|
214
|
+
locals, and conditional compilation.
|
|
215
|
+
|
|
216
|
+
`On Error GoTo` does not unwind the stack: the handler runs where the
|
|
217
|
+
error happened, so `Resume Next` hands control back to the statement
|
|
218
|
+
after it even inside a loop, which is what a loop-and-skip macro
|
|
219
|
+
depends on.
|
|
220
|
+
|
|
221
|
+
**The runtime library.** The conversion, maths, string, date, array and
|
|
222
|
+
information functions, `Format` (shared with the Access engine, which is
|
|
223
|
+
the same function in Office), `MsgBox` and `InputBox`, and the financial
|
|
224
|
+
functions that are closed forms.
|
|
225
|
+
|
|
226
|
+
`MsgBox` does not block. It records the message and answers from a
|
|
227
|
+
queue the caller primed, so a macro that would have stopped for a prompt
|
|
228
|
+
runs to the end and the prompt is visible afterwards.
|
|
229
|
+
|
|
230
|
+
**Excel.** Application, Workbooks, Workbook, Sheets, Worksheet, Range
|
|
231
|
+
with multiple areas, Font, Interior, Names, Queries, Shapes, and a
|
|
232
|
+
short list of worksheet functions. Cells hold values, formulas, number
|
|
233
|
+
formats and a little formatting.
|
|
234
|
+
|
|
235
|
+
**Word.** Application, Documents, Document, Range, Paragraphs,
|
|
236
|
+
Paragraph, Shapes, InlineShapes, Shape, WrapFormat, TextFrame and
|
|
237
|
+
TextRange. The text is read and can be assigned; a document whose
|
|
238
|
+
text nobody assigned and whose shapes nobody moved is written back byte
|
|
239
|
+
for byte, so styles, headers and numbering survive a shape edit.
|
|
240
|
+
|
|
241
|
+
**PowerPoint.** Application, Presentations, Presentation, PageSetup,
|
|
242
|
+
Slides, Slide, Shapes, Shape, GroupItems, TextFrame, TextRange,
|
|
243
|
+
ActionSettings and ActionSetting, with `ActiveWindow.View.Slide` to
|
|
244
|
+
reach the slide in view.
|
|
245
|
+
|
|
246
|
+
**Shapes, on all three.** Adding, reading, moving, resizing, renaming,
|
|
247
|
+
retyping and deleting: `AddShape`, `AddTextbox`, `AddLine`, and
|
|
248
|
+
`AddFormControl` in Excel. The macro a click runs is `Shape.OnAction`
|
|
249
|
+
in Excel and `ActionSettings(ppMouseClick).Run` in PowerPoint; Word has
|
|
250
|
+
no such thing, and says so rather than pretending.
|
|
251
|
+
|
|
252
|
+
An Excel form control is four parts of the workbook at once -- the
|
|
253
|
+
hidden shape in the drawing, the sheet's own record of it, a part
|
|
254
|
+
saying which control it is, and the VML Excel draws it from -- and its
|
|
255
|
+
macro lives in two of them. Making one and wiring it up writes all
|
|
256
|
+
four, which is what lets a button a macro created open as a button.
|
|
257
|
+
|
|
258
|
+
**Formulas are calculated.** `pyopenvba.formula` parses and evaluates
|
|
259
|
+
them; the workbook keeps track of which cells are stale and what feeds
|
|
260
|
+
what. Calculation is on demand: reading a stale cell works it out, and
|
|
261
|
+
working it out works out whatever it reads, so nothing is ordered up
|
|
262
|
+
front and INDIRECT is no harder than a plain reference. Writing a cell
|
|
263
|
+
spoils whatever reads it, a volatile function is recomputed every time,
|
|
264
|
+
a formula written to a block or copied has its relative references
|
|
265
|
+
moved, a circular reference leaves a zero and is recorded, and manual
|
|
266
|
+
calculation mode keeps the old value until something calls Calculate,
|
|
267
|
+
which is what Excel shows until F9.
|
|
268
|
+
|
|
269
|
+
About a hundred worksheet functions are implemented, chosen by what
|
|
270
|
+
macros actually call: the aggregations and their -IF and -IFS forms, the
|
|
271
|
+
lookups, the text and date families, the information tests, and the
|
|
272
|
+
logical functions. Anything else Excel has says so by name rather than
|
|
273
|
+
answering `#NAME?`, which is reserved for a function Excel has not got
|
|
274
|
+
either.
|
|
275
|
+
|
|
276
|
+
**Power Query is evaluated.** `pyopenvba.mlang` is an M evaluator, so
|
|
277
|
+
`WorkbookQuery.Refresh` works out the query's rows and writes them to
|
|
278
|
+
the sheet it loads to; the table and its queryTable follow, and a save
|
|
279
|
+
writes what the refresh produced. Bindings are lazy as M's are, one
|
|
280
|
+
query can name another, and `Excel.CurrentWorkbook()` reads the tables
|
|
281
|
+
and named ranges of the workbook being refreshed. A source that cannot
|
|
282
|
+
be reached from a library with no network and no drivers -- a database,
|
|
283
|
+
a web service, a folder somewhere else -- says so by name.
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
app = ExcelApplication.open("sales.xlsx")
|
|
287
|
+
app.refresh_query("Summary") # rows, headers first
|
|
288
|
+
app.save("sales_out.xlsx")
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
**Files.** A workbook is read from `.xlsx`, `.xlsm` or `.xlam`, a
|
|
292
|
+
document from `.docx`, `.docm` or `.dotm`, and a presentation from
|
|
293
|
+
`.pptx`, `.pptm` or `.potm`, each written back with only what changed
|
|
294
|
+
rewritten: a sheet, a slide or a body nobody touched keeps its bytes,
|
|
295
|
+
and an unchanged file saves byte for byte. A workbook made from
|
|
296
|
+
nothing is written from the template captured from a freshly
|
|
297
|
+
Excel-authored file; Word and PowerPoint have no such template yet, so
|
|
298
|
+
a document or a presentation is edited rather than created.
|
|
299
|
+
|
|
300
|
+
## What is not
|
|
301
|
+
|
|
302
|
+
- **Array formulas do not spill.** An array result lands in the one cell
|
|
303
|
+
that holds the formula and shows its first element, which is what
|
|
304
|
+
Excel did before dynamic arrays. Implicit intersection is applied to
|
|
305
|
+
a multi-cell reference beside an operator, as Excel's `@` does.
|
|
306
|
+
- **Power Query reaches nothing off the machine.** The language is
|
|
307
|
+
evaluated and a local source is read, but `Sql.Database`, `Web.Contents`,
|
|
308
|
+
`OData.Feed` and the rest of the connectors report themselves rather
|
|
309
|
+
than opening a connection. 224 of M's 859 library names are
|
|
310
|
+
implemented; the others are named as gaps, not answered wrongly.
|
|
311
|
+
- **No events.** `WithEvents` sinks are not connected and `RaiseEvent`
|
|
312
|
+
says so. A `Worksheet_Change` handler will not fire when a macro
|
|
313
|
+
writes a cell.
|
|
314
|
+
- **Nothing outside the model.** File I/O, the file system verbs, the
|
|
315
|
+
registry, `Shell`, `SendKeys`, `CreateObject` and `Declare` into a DLL
|
|
316
|
+
all report themselves unsupported rather than reaching the real
|
|
317
|
+
machine.
|
|
318
|
+
- **`.xlsb` cells.** The binary sheet part is not read; its VBA project
|
|
319
|
+
is readable through `ExcelFile` as before. The same goes for the
|
|
320
|
+
legacy `.doc`, `.ppt` and `.xls`, whose text and shapes live in
|
|
321
|
+
binary streams this does not read.
|
|
322
|
+
- **No document or presentation from nothing.** Word and PowerPoint
|
|
323
|
+
open a real file and edit it; only Excel has a template to write one
|
|
324
|
+
from.
|
|
325
|
+
- **Grouping is not done here.** A group in a file is read, and its
|
|
326
|
+
members are reachable through `GroupItems`, but `ShapeRange.Group`
|
|
327
|
+
reports itself rather than making one.
|
|
328
|
+
- **A picture or a chart is read, not made.** `AddPicture` says so;
|
|
329
|
+
the media and the chart parts a new one needs are not written.
|
|
330
|
+
- A type-suffixed name is the same variable as the name without it, so
|
|
331
|
+
the implicit-typing trick where `a%` and `a$` are two variables is not
|
|
332
|
+
modelled.
|
|
333
|
+
|
|
334
|
+
## Where the parts live
|
|
335
|
+
|
|
336
|
+
| Module | What it is |
|
|
337
|
+
| --- | --- |
|
|
338
|
+
| `interpreter/_lex.py` | Tokenizer |
|
|
339
|
+
| `interpreter/_preprocess.py` | `#If` resolved before parsing |
|
|
340
|
+
| `interpreter/_parse.py` | Parser, producing `_ast.py` |
|
|
341
|
+
| `interpreter/_values.py` | Variant semantics: conversions and operators |
|
|
342
|
+
| `interpreter/_objects.py` | How VBA reaches an object and how one answers |
|
|
343
|
+
| `interpreter/_intrinsics.py` | The VBA runtime library |
|
|
344
|
+
| `interpreter/_runtime.py` | The interpreter itself |
|
|
345
|
+
| `interpreter/_inventory.py` | Which members the real classes have |
|
|
346
|
+
| `apps/excel/_model.py` | Excel's object model |
|
|
347
|
+
| `apps/excel/_calc.py` | Which cells are stale, and what they come to |
|
|
348
|
+
| `formula/_parse.py` | The formula grammar |
|
|
349
|
+
| `formula/_engine.py` | Evaluating one formula against a grid |
|
|
350
|
+
| `formula/_functions.py` | The worksheet functions |
|
|
351
|
+
| `apps/excel/_io.py` | Reading and writing the workbook file |
|
|
352
|
+
| `apps/excel/_bridge.py` | What a project sees when Excel is the host |
|
|
353
|
+
| `apps/excel/_shapes.py` | A sheet's shapes, as a macro reaches them |
|
|
354
|
+
| `apps/word/` | Word's object model, its bridge and its file |
|
|
355
|
+
| `apps/powerpoint/` | PowerPoint's, the same three |
|
|
356
|
+
| `shapes/_values.py` | What a shape is, in all three hosts' words |
|
|
357
|
+
| `shapes/_drawingml.py` | The DrawingML the three of them share |
|
|
358
|
+
| `shapes/_xlsx.py` | A worksheet's drawing layer and its controls |
|
|
359
|
+
| `shapes/_pptx.py` | A slide's shape tree |
|
|
360
|
+
| `shapes/_docx.py` | A document's drawings, floating and in line |
|
|
361
|
+
| `apps/excel/_refresh.py` | A query evaluated and landed on a sheet |
|
|
362
|
+
| `mlang/_parse.py` | The M grammar |
|
|
363
|
+
| `mlang/_eval.py` | Evaluating M, lazily |
|
|
364
|
+
| `mlang/_library.py` | The M library |
|
|
365
|
+
| `mlang/_inventory.py` | Which names Power Query has, from `#shared` |
|
|
366
|
+
|
|
367
|
+
## What comes next
|
|
368
|
+
|
|
369
|
+
Access gets the same shape as the other three: an object model under
|
|
370
|
+
`apps/`, a bridge, and a measured probe file. The document-module
|
|
371
|
+
events are the other open piece, so that writing a cell from a macro
|
|
372
|
+
fires `Worksheet_Change`, and after that the parts of each host's model
|
|
373
|
+
that no probe has reached yet -- Word's tables and headers,
|
|
374
|
+
PowerPoint's slide masters, Excel's pivot tables.
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pyOpenVBA"
|
|
7
|
-
version = "
|
|
7
|
+
version = "6.0.0"
|
|
8
8
|
description = "Read and write VBA macros and Excel Power Query in pure Python, no dependencies."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -88,6 +88,7 @@ include = [
|
|
|
88
88
|
"docs/architecture.md",
|
|
89
89
|
"docs/power_query.md",
|
|
90
90
|
"docs/roadmap.md",
|
|
91
|
+
"docs/vba_runtime.md",
|
|
91
92
|
"docs/ms-ovba-implementation-guide_v2.md",
|
|
92
93
|
]
|
|
93
94
|
|