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.
Files changed (141) hide show
  1. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/.gitignore +3 -0
  2. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/PKG-INFO +111 -1
  3. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/README.md +110 -0
  4. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/docs/roadmap.md +23 -1
  5. pyopenvba-6.0.0/docs/vba_runtime.md +374 -0
  6. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/pyproject.toml +2 -1
  7. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/__init__.py +1 -1
  8. pyopenvba-6.0.0/src/pyopenvba/_a1.py +205 -0
  9. pyopenvba-6.0.0/src/pyopenvba/_xml.py +70 -0
  10. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_sql.py +4 -1
  11. pyopenvba-6.0.0/src/pyopenvba/apps/__init__.py +6 -0
  12. pyopenvba-6.0.0/src/pyopenvba/apps/excel/__init__.py +292 -0
  13. pyopenvba-6.0.0/src/pyopenvba/apps/excel/_bridge.py +75 -0
  14. pyopenvba-6.0.0/src/pyopenvba/apps/excel/_calc.py +310 -0
  15. pyopenvba-6.0.0/src/pyopenvba/apps/excel/_io.py +1157 -0
  16. pyopenvba-6.0.0/src/pyopenvba/apps/excel/_model.py +2047 -0
  17. pyopenvba-6.0.0/src/pyopenvba/apps/excel/_refresh.py +264 -0
  18. pyopenvba-6.0.0/src/pyopenvba/apps/excel/_shapes.py +481 -0
  19. pyopenvba-6.0.0/src/pyopenvba/apps/powerpoint/__init__.py +220 -0
  20. pyopenvba-6.0.0/src/pyopenvba/apps/powerpoint/_bridge.py +94 -0
  21. pyopenvba-6.0.0/src/pyopenvba/apps/powerpoint/_io.py +118 -0
  22. pyopenvba-6.0.0/src/pyopenvba/apps/powerpoint/_model.py +790 -0
  23. pyopenvba-6.0.0/src/pyopenvba/apps/word/__init__.py +179 -0
  24. pyopenvba-6.0.0/src/pyopenvba/apps/word/_bridge.py +103 -0
  25. pyopenvba-6.0.0/src/pyopenvba/apps/word/_io.py +80 -0
  26. pyopenvba-6.0.0/src/pyopenvba/apps/word/_model.py +646 -0
  27. pyopenvba-6.0.0/src/pyopenvba/exceptions.py +92 -0
  28. pyopenvba-6.0.0/src/pyopenvba/formula/__init__.py +59 -0
  29. pyopenvba-6.0.0/src/pyopenvba/formula/_engine.py +287 -0
  30. pyopenvba-6.0.0/src/pyopenvba/formula/_functions.py +1455 -0
  31. pyopenvba-6.0.0/src/pyopenvba/formula/_inventory.py +54 -0
  32. pyopenvba-6.0.0/src/pyopenvba/formula/_parse.py +444 -0
  33. pyopenvba-6.0.0/src/pyopenvba/formula/_values.py +334 -0
  34. pyopenvba-6.0.0/src/pyopenvba/interpreter/__init__.py +89 -0
  35. pyopenvba-6.0.0/src/pyopenvba/interpreter/_ast.py +439 -0
  36. pyopenvba-6.0.0/src/pyopenvba/interpreter/_constants_data.py +5330 -0
  37. pyopenvba-6.0.0/src/pyopenvba/interpreter/_intrinsics.py +1261 -0
  38. pyopenvba-6.0.0/src/pyopenvba/interpreter/_inventory.py +46 -0
  39. pyopenvba-6.0.0/src/pyopenvba/interpreter/_inventory_data.py +660 -0
  40. pyopenvba-6.0.0/src/pyopenvba/interpreter/_lex.py +304 -0
  41. pyopenvba-6.0.0/src/pyopenvba/interpreter/_objects.py +312 -0
  42. pyopenvba-6.0.0/src/pyopenvba/interpreter/_parse.py +1274 -0
  43. pyopenvba-6.0.0/src/pyopenvba/interpreter/_preprocess.py +246 -0
  44. pyopenvba-6.0.0/src/pyopenvba/interpreter/_runtime.py +1871 -0
  45. pyopenvba-6.0.0/src/pyopenvba/interpreter/_values.py +1116 -0
  46. pyopenvba-6.0.0/src/pyopenvba/mlang/__init__.py +59 -0
  47. pyopenvba-6.0.0/src/pyopenvba/mlang/_eval.py +530 -0
  48. pyopenvba-6.0.0/src/pyopenvba/mlang/_inventory.py +26 -0
  49. pyopenvba-6.0.0/src/pyopenvba/mlang/_inventory_data.py +193 -0
  50. pyopenvba-6.0.0/src/pyopenvba/mlang/_library.py +2099 -0
  51. pyopenvba-6.0.0/src/pyopenvba/mlang/_parse.py +627 -0
  52. pyopenvba-6.0.0/src/pyopenvba/mlang/_values.py +397 -0
  53. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/powerquery/_sheets.py +3 -37
  54. pyopenvba-6.0.0/src/pyopenvba/shapes/__init__.py +34 -0
  55. pyopenvba-6.0.0/src/pyopenvba/shapes/_docx.py +404 -0
  56. pyopenvba-6.0.0/src/pyopenvba/shapes/_docx_text.py +126 -0
  57. pyopenvba-6.0.0/src/pyopenvba/shapes/_drawingml.py +195 -0
  58. pyopenvba-6.0.0/src/pyopenvba/shapes/_pptx.py +248 -0
  59. pyopenvba-6.0.0/src/pyopenvba/shapes/_values.py +218 -0
  60. pyopenvba-6.0.0/src/pyopenvba/shapes/_xlsx.py +737 -0
  61. pyopenvba-5.2.4/src/pyopenvba/exceptions.py +0 -35
  62. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/LICENSE.md +0 -0
  63. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/docs/architecture.md +0 -0
  64. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/docs/ms-ovba-implementation-guide_v2.md +0 -0
  65. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/docs/power_query.md +0 -0
  66. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/docs/research/access_complex/README.md +0 -0
  67. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/docs/research/access_designs/README.md +0 -0
  68. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/docs/research/access_macros/README.md +0 -0
  69. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/docs/research/access_write/README.md +0 -0
  70. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/docs/research/pcode/README.md +0 -0
  71. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/__main__.py +0 -0
  72. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_deflate.py +0 -0
  73. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_host.py +0 -0
  74. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_oforms_pages.py +0 -0
  75. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_oforms_records.py +0 -0
  76. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_ppt_container.py +0 -0
  77. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/__init__.py +0 -0
  78. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/blank_files/blank_database.accdb +0 -0
  79. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/blank_files/blank_database_module.accdb +0 -0
  80. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/blank_files/blank_document.docm +0 -0
  81. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/blank_files/blank_excel_addin.xlam +0 -0
  82. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/blank_files/blank_presentation.pptm +0 -0
  83. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/blank_files/blank_workbook.xlsb +0 -0
  84. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/blank_files/blank_workbook.xlsm +0 -0
  85. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/blank_files/blank_workbook.xlsx +0 -0
  86. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/blank_files/engine_skeleton.accdb +0 -0
  87. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/blank_files/engine_skeleton.mdb +0 -0
  88. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/designs/form.blob +0 -0
  89. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/designs/form.lvprop +0 -0
  90. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/designs/form.propdata +0 -0
  91. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/designs/form.prototypes +0 -0
  92. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/designs/form.typeinfo +0 -0
  93. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/designs/report.blob +0 -0
  94. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/designs/report.lvprop +0 -0
  95. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/designs/report.propdata +0 -0
  96. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/designs/report.prototypes +0 -0
  97. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/_templates/designs/report.typeinfo +0 -0
  98. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/__init__.py +0 -0
  99. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_alloc.py +0 -0
  100. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_btree.py +0 -0
  101. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_collation.py +0 -0
  102. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_collation_general_legacy.py +0 -0
  103. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_compact.py +0 -0
  104. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_complex.py +0 -0
  105. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_datapage.py +0 -0
  106. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_ddl.py +0 -0
  107. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_designs.py +0 -0
  108. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_facade.py +0 -0
  109. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_format.py +0 -0
  110. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_index.py +0 -0
  111. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_lval.py +0 -0
  112. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_macros.py +0 -0
  113. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_pages.py +0 -0
  114. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_props.py +0 -0
  115. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_queries.py +0 -0
  116. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_rows.py +0 -0
  117. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_schema.py +0 -0
  118. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_storage.py +0 -0
  119. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_tdef.py +0 -0
  120. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_validate.py +0 -0
  121. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/_vba.py +0 -0
  122. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access/database.py +0 -0
  123. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/access_read.py +0 -0
  124. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/cfb.py +0 -0
  125. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/excel.py +0 -0
  126. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/forms.py +0 -0
  127. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/powerpoint.py +0 -0
  128. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/powerquery/__init__.py +0 -0
  129. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/powerquery/_binary.py +0 -0
  130. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/powerquery/_files.py +0 -0
  131. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/powerquery/_mashup.py +0 -0
  132. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/powerquery/_metadata.py +0 -0
  133. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/powerquery/_opc.py +0 -0
  134. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/powerquery/_package.py +0 -0
  135. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/powerquery/_refresh.py +0 -0
  136. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/powerquery/_section.py +0 -0
  137. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/powerquery/workbook.py +0 -0
  138. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/vba.py +0 -0
  139. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/vba_pcode.py +0 -0
  140. {pyopenvba-5.2.4 → pyopenvba-6.0.0}/src/pyopenvba/word.py +0 -0
  141. {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: 5.2.4
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
- _No open near-term items: all in-scope gates are PASS. See the "Out of scope" section below for explicitly deferred work._
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 = "5.2.4"
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