pyOpenVBA 1.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-1.0.0/.gitignore +41 -0
- pyopenvba-1.0.0/LICENSE.md +21 -0
- pyopenvba-1.0.0/PKG-INFO +285 -0
- pyopenvba-1.0.0/README.md +254 -0
- pyopenvba-1.0.0/docs/architecture.md +266 -0
- pyopenvba-1.0.0/docs/ms-ovba-implementation-guide_v2.md +970 -0
- pyopenvba-1.0.0/docs/roadmap.md +96 -0
- pyopenvba-1.0.0/pyproject.toml +76 -0
- pyopenvba-1.0.0/src/pyopenvba/__init__.py +80 -0
- pyopenvba-1.0.0/src/pyopenvba/__main__.py +68 -0
- pyopenvba-1.0.0/src/pyopenvba/cfb.py +931 -0
- pyopenvba-1.0.0/src/pyopenvba/excel.py +487 -0
- pyopenvba-1.0.0/src/pyopenvba/exceptions.py +17 -0
- pyopenvba-1.0.0/src/pyopenvba/vba.py +1543 -0
- pyopenvba-1.0.0/tests/fuzz_corpus/README.md +42 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.pyo
|
|
5
|
+
*.pyd
|
|
6
|
+
*.so
|
|
7
|
+
*.egg
|
|
8
|
+
*.egg-info/
|
|
9
|
+
dist/
|
|
10
|
+
build/
|
|
11
|
+
.eggs/
|
|
12
|
+
|
|
13
|
+
# Virtual environments
|
|
14
|
+
.venv/
|
|
15
|
+
venv/
|
|
16
|
+
env/
|
|
17
|
+
|
|
18
|
+
# Distribution / packaging
|
|
19
|
+
*.whl
|
|
20
|
+
*.tar.gz
|
|
21
|
+
MANIFEST
|
|
22
|
+
|
|
23
|
+
# Pytest
|
|
24
|
+
.pytest_cache/
|
|
25
|
+
.cache/
|
|
26
|
+
|
|
27
|
+
# Type checking
|
|
28
|
+
.pyright/
|
|
29
|
+
|
|
30
|
+
# Editor / OS
|
|
31
|
+
.vscode/settings.json
|
|
32
|
+
.DS_Store
|
|
33
|
+
Thumbs.db
|
|
34
|
+
|
|
35
|
+
# Test artifacts
|
|
36
|
+
tests/fixtures/*.xlsm
|
|
37
|
+
tests/fixtures/*.xls
|
|
38
|
+
|
|
39
|
+
# Manually-generated Excel verification set (rebuilt by
|
|
40
|
+
# scripts/build_excel_verification_set.py)
|
|
41
|
+
tests/live_excel_testing/_excel_verify/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 William Smith
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pyopenvba-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyOpenVBA
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Read and write VBA macros inside Excel workbooks (.xlsm, .xlsb, .xlam, .xls) in pure Python, no dependencies.
|
|
5
|
+
Project-URL: Homepage, https://github.com/WilliamSmithEdward/pyOpenVBA
|
|
6
|
+
Project-URL: Repository, https://github.com/WilliamSmithEdward/pyOpenVBA
|
|
7
|
+
Project-URL: Issues, https://github.com/WilliamSmithEdward/pyOpenVBA/issues
|
|
8
|
+
Project-URL: Documentation, https://github.com/WilliamSmithEdward/pyOpenVBA#readme
|
|
9
|
+
Author-email: William Smith <williamsmithe@icloud.com>
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE.md
|
|
12
|
+
Keywords: excel,macros,ms-cfb,ms-ovba,office,vba,xlam,xls,xlsb,xlsm
|
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
25
|
+
Classifier: Topic :: Office/Business
|
|
26
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
27
|
+
Classifier: Topic :: System :: Archiving
|
|
28
|
+
Classifier: Typing :: Typed
|
|
29
|
+
Requires-Python: >=3.10
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# pyOpenVBA
|
|
33
|
+
|
|
34
|
+
**Read and write the VBA macros inside Excel workbooks, in pure Python.**
|
|
35
|
+
|
|
36
|
+
No external dependencies. No Excel install required. Works on Windows,
|
|
37
|
+
macOS, and Linux. Python 3.10 or newer.
|
|
38
|
+
|
|
39
|
+
Supports `.xlsm`, `.xlsb`, `.xlam`, and legacy `.xls`.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Why use this?
|
|
44
|
+
|
|
45
|
+
Several excellent Python tools already exist for **reading** VBA out of
|
|
46
|
+
Excel files (oletools, olefile, and friends), and they remain the right
|
|
47
|
+
choice for forensics, malware analysis, and audit use-cases. pyOpenVBA
|
|
48
|
+
focuses on the next step: safely **writing** changes back so the
|
|
49
|
+
workbook still opens cleanly in Excel.
|
|
50
|
+
|
|
51
|
+
The write path is the whole point of the library:
|
|
52
|
+
|
|
53
|
+
- **Replace** a module's source in place.
|
|
54
|
+
- **Add** a new standard module, class module, or document/UserForm
|
|
55
|
+
code-behind.
|
|
56
|
+
- **Rename** any module (the CFB stream, `dir` record, `PROJECT`
|
|
57
|
+
declaration, `PROJECTwm` name map, and `Attribute VB_Name` are all
|
|
58
|
+
updated in lockstep).
|
|
59
|
+
- **Delete** a module cleanly.
|
|
60
|
+
- **Save** the workbook and have it reopen in Excel with no "we found
|
|
61
|
+
a problem with some content" repair dialog. Every supported format
|
|
62
|
+
(`.xlsm`, `.xlsb`, `.xlam`, `.xls`) is verified against live Excel.
|
|
63
|
+
- **Safely refuse** to corrupt password-protected or digitally signed
|
|
64
|
+
projects unless you explicitly opt in.
|
|
65
|
+
|
|
66
|
+
That makes it a good fit for:
|
|
67
|
+
|
|
68
|
+
- **Version-controlling your VBA** in git like normal source code, then
|
|
69
|
+
pushing edits back without ever opening Excel.
|
|
70
|
+
- **Diffing** two workbooks to see what changed in `Module1`.
|
|
71
|
+
- **Generating or updating macros from a script** without scripting
|
|
72
|
+
Excel through COM automation.
|
|
73
|
+
- **Reading and writing macros on a server** (Linux / CI) where Excel
|
|
74
|
+
is not installed.
|
|
75
|
+
|
|
76
|
+
pyOpenVBA is a complete read-and-write library, so it covers the full
|
|
77
|
+
lifecycle of a VBA project in one place: extract, edit, version, write
|
|
78
|
+
back, and verify. If you only ever need to read, the existing tools
|
|
79
|
+
remain a solid choice; if you might ever need to write, start here and
|
|
80
|
+
you will not have to switch later.
|
|
81
|
+
|
|
82
|
+
## Installation
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
pip install pyOpenVBA
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Or from source:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
git clone https://github.com/WilliamSmithEdward/pyOpenVBA
|
|
92
|
+
cd pyOpenVBA
|
|
93
|
+
pip install -e .
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
That's it. There are no other dependencies.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## 30-second tour
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from pyopenvba import ExcelFile
|
|
104
|
+
|
|
105
|
+
with ExcelFile("workbook.xlsm") as wb:
|
|
106
|
+
# 1. List all VBA modules in the workbook.
|
|
107
|
+
print(wb.module_names())
|
|
108
|
+
# ['ThisWorkbook', 'Sheet1', 'Module1']
|
|
109
|
+
|
|
110
|
+
# 2. Read a module's source as a string.
|
|
111
|
+
source = wb.get_module("Module1")
|
|
112
|
+
print(source)
|
|
113
|
+
|
|
114
|
+
# 3. Edit a module and save the workbook.
|
|
115
|
+
wb.set_module("Module1", 'Sub Hello()\r\n MsgBox "hi"\r\nEnd Sub\r\n')
|
|
116
|
+
wb.save() # overwrites the original file
|
|
117
|
+
# wb.save("edited.xlsm") # ...or save to a new file
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
That is the entire core API. Three methods.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Add, rename, or delete a module
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from pyopenvba import ExcelFile, VBAModuleKind
|
|
128
|
+
|
|
129
|
+
with ExcelFile("workbook.xlsm") as wb:
|
|
130
|
+
project = wb.vba_project()
|
|
131
|
+
|
|
132
|
+
project.add_module(
|
|
133
|
+
"NewModule",
|
|
134
|
+
"Sub Hi()\r\n MsgBox \"hi\"\r\nEnd Sub\r\n",
|
|
135
|
+
kind=VBAModuleKind.standard,
|
|
136
|
+
)
|
|
137
|
+
project.rename_module("OldName", "NewName")
|
|
138
|
+
project.delete_module("Obsolete")
|
|
139
|
+
|
|
140
|
+
wb.save("out.xlsm")
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Edit your macros as files on disk (recommended workflow)
|
|
146
|
+
|
|
147
|
+
This is the easiest way to manage VBA in a git repo. Export every
|
|
148
|
+
module to a folder, edit the files in any text editor, then push the
|
|
149
|
+
changes back into the workbook.
|
|
150
|
+
|
|
151
|
+
From the command line:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
# 1. Pull every module out of the workbook into ./vba/
|
|
155
|
+
python -m pyopenvba pull workbook.xlsm ./vba
|
|
156
|
+
|
|
157
|
+
# 2. ...edit ./vba/Module1.bas in your editor of choice...
|
|
158
|
+
|
|
159
|
+
# 3. Push your edits back into the workbook
|
|
160
|
+
python -m pyopenvba push ./vba workbook.xlsm
|
|
161
|
+
|
|
162
|
+
# Bonus: list modules without extracting anything
|
|
163
|
+
python -m pyopenvba ls workbook.xlsm
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
From Python:
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
from pyopenvba import pull, push
|
|
170
|
+
|
|
171
|
+
pull("workbook.xlsm", "./vba")
|
|
172
|
+
# ...edit files...
|
|
173
|
+
push("./vba", "workbook.xlsm") # in place
|
|
174
|
+
push("./vba", "workbook.xlsm", out="edited.xlsm") # to a new file
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Module files use the extensions VBA already uses: `.bas` for standard
|
|
178
|
+
modules, `.cls` for class modules, `.frm` for UserForm code-behind.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Supported formats
|
|
183
|
+
|
|
184
|
+
| Extension | What it is | Read | Write |
|
|
185
|
+
|-----------|------------------------------|:----:|:-----:|
|
|
186
|
+
| `.xlsm` | Macro-enabled workbook | yes | yes |
|
|
187
|
+
| `.xlsb` | Binary workbook | yes | yes |
|
|
188
|
+
| `.xlam` | Macro-enabled add-in | yes | yes |
|
|
189
|
+
| `.xls` | Legacy (Excel 97-2003) | yes | yes |
|
|
190
|
+
|
|
191
|
+
Every save is verified to reopen in Excel **without** the "we found a
|
|
192
|
+
problem with some content" repair dialog.
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Safety guards
|
|
197
|
+
|
|
198
|
+
`save()` refuses to silently produce a broken workbook.
|
|
199
|
+
|
|
200
|
+
### Password-protected projects
|
|
201
|
+
|
|
202
|
+
If the VBA project is password-protected, any mutation will raise
|
|
203
|
+
`VBAProjectError` unless you explicitly opt in:
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
wb.save(allow_protected=True)
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
The library never tries to decrypt or change the password - it just
|
|
210
|
+
preserves the existing protection bytes verbatim. The resulting
|
|
211
|
+
workbook still requires the original password to open the VBE.
|
|
212
|
+
|
|
213
|
+
### Digitally-signed projects
|
|
214
|
+
|
|
215
|
+
A digital signature is invalidated by *any* change to the macros. On
|
|
216
|
+
mutation, the library drops the stale signature streams and emits a
|
|
217
|
+
`UserWarning` so you know trust has been removed:
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
import warnings
|
|
221
|
+
warnings.filterwarnings("error", category=UserWarning) # treat as fatal
|
|
222
|
+
|
|
223
|
+
# ...or silence the warning if you accept the consequence:
|
|
224
|
+
wb.save(allow_invalidate_signature=True)
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## What's out of scope
|
|
230
|
+
|
|
231
|
+
This library is intentionally focused on **module source code**. The
|
|
232
|
+
following are preserved byte-for-byte but not interpreted:
|
|
233
|
+
|
|
234
|
+
- UserForm **layout** (controls, properties, positions). Editing the
|
|
235
|
+
**code-behind** of a UserForm works fine; editing the design surface
|
|
236
|
+
does not.
|
|
237
|
+
- VBA project password decryption / re-encryption.
|
|
238
|
+
- Re-signing digitally signed projects.
|
|
239
|
+
- ActiveX license editing.
|
|
240
|
+
|
|
241
|
+
See [docs/roadmap.md](docs/roadmap.md) for the full feature matrix.
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## Architecture
|
|
246
|
+
|
|
247
|
+
```
|
|
248
|
+
src/pyopenvba/
|
|
249
|
+
__init__.py public API (ExcelFile, pull, push, exceptions)
|
|
250
|
+
excel.py ExcelFile facade (ZIP / CFB dispatch, pull/push helpers)
|
|
251
|
+
vba.py VBA project parser + MS-OVBA codec
|
|
252
|
+
cfb.py MS-CFB (Compound File Binary) parser/writer
|
|
253
|
+
exceptions.py custom exception hierarchy
|
|
254
|
+
__main__.py `python -m pyopenvba {pull,push,ls}` CLI
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
For deeper documentation:
|
|
258
|
+
|
|
259
|
+
- [docs/architecture.md](docs/architecture.md) - internal module layout.
|
|
260
|
+
- [docs/ms-ovba-implementation-guide_v2.md](docs/ms-ovba-implementation-guide_v2.md) -
|
|
261
|
+
language-agnostic guide for re-implementing MS-OVBA in another
|
|
262
|
+
language.
|
|
263
|
+
- [docs/roadmap.md](docs/roadmap.md) - per-feature implementation status.
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## Contributing
|
|
268
|
+
|
|
269
|
+
Bug reports, weird workbooks that break the library, and PRs are all
|
|
270
|
+
welcome. Please include the workbook (or a minimal redacted version)
|
|
271
|
+
when filing a parsing bug.
|
|
272
|
+
|
|
273
|
+
Run the test suite:
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
pip install pytest pyright
|
|
277
|
+
pytest
|
|
278
|
+
pyright src tests
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## License
|
|
284
|
+
|
|
285
|
+
[MIT](LICENSE.md).
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# pyOpenVBA
|
|
2
|
+
|
|
3
|
+
**Read and write the VBA macros inside Excel workbooks, in pure Python.**
|
|
4
|
+
|
|
5
|
+
No external dependencies. No Excel install required. Works on Windows,
|
|
6
|
+
macOS, and Linux. Python 3.10 or newer.
|
|
7
|
+
|
|
8
|
+
Supports `.xlsm`, `.xlsb`, `.xlam`, and legacy `.xls`.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Why use this?
|
|
13
|
+
|
|
14
|
+
Several excellent Python tools already exist for **reading** VBA out of
|
|
15
|
+
Excel files (oletools, olefile, and friends), and they remain the right
|
|
16
|
+
choice for forensics, malware analysis, and audit use-cases. pyOpenVBA
|
|
17
|
+
focuses on the next step: safely **writing** changes back so the
|
|
18
|
+
workbook still opens cleanly in Excel.
|
|
19
|
+
|
|
20
|
+
The write path is the whole point of the library:
|
|
21
|
+
|
|
22
|
+
- **Replace** a module's source in place.
|
|
23
|
+
- **Add** a new standard module, class module, or document/UserForm
|
|
24
|
+
code-behind.
|
|
25
|
+
- **Rename** any module (the CFB stream, `dir` record, `PROJECT`
|
|
26
|
+
declaration, `PROJECTwm` name map, and `Attribute VB_Name` are all
|
|
27
|
+
updated in lockstep).
|
|
28
|
+
- **Delete** a module cleanly.
|
|
29
|
+
- **Save** the workbook and have it reopen in Excel with no "we found
|
|
30
|
+
a problem with some content" repair dialog. Every supported format
|
|
31
|
+
(`.xlsm`, `.xlsb`, `.xlam`, `.xls`) is verified against live Excel.
|
|
32
|
+
- **Safely refuse** to corrupt password-protected or digitally signed
|
|
33
|
+
projects unless you explicitly opt in.
|
|
34
|
+
|
|
35
|
+
That makes it a good fit for:
|
|
36
|
+
|
|
37
|
+
- **Version-controlling your VBA** in git like normal source code, then
|
|
38
|
+
pushing edits back without ever opening Excel.
|
|
39
|
+
- **Diffing** two workbooks to see what changed in `Module1`.
|
|
40
|
+
- **Generating or updating macros from a script** without scripting
|
|
41
|
+
Excel through COM automation.
|
|
42
|
+
- **Reading and writing macros on a server** (Linux / CI) where Excel
|
|
43
|
+
is not installed.
|
|
44
|
+
|
|
45
|
+
pyOpenVBA is a complete read-and-write library, so it covers the full
|
|
46
|
+
lifecycle of a VBA project in one place: extract, edit, version, write
|
|
47
|
+
back, and verify. If you only ever need to read, the existing tools
|
|
48
|
+
remain a solid choice; if you might ever need to write, start here and
|
|
49
|
+
you will not have to switch later.
|
|
50
|
+
|
|
51
|
+
## Installation
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install pyOpenVBA
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Or from source:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
git clone https://github.com/WilliamSmithEdward/pyOpenVBA
|
|
61
|
+
cd pyOpenVBA
|
|
62
|
+
pip install -e .
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
That's it. There are no other dependencies.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## 30-second tour
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from pyopenvba import ExcelFile
|
|
73
|
+
|
|
74
|
+
with ExcelFile("workbook.xlsm") as wb:
|
|
75
|
+
# 1. List all VBA modules in the workbook.
|
|
76
|
+
print(wb.module_names())
|
|
77
|
+
# ['ThisWorkbook', 'Sheet1', 'Module1']
|
|
78
|
+
|
|
79
|
+
# 2. Read a module's source as a string.
|
|
80
|
+
source = wb.get_module("Module1")
|
|
81
|
+
print(source)
|
|
82
|
+
|
|
83
|
+
# 3. Edit a module and save the workbook.
|
|
84
|
+
wb.set_module("Module1", 'Sub Hello()\r\n MsgBox "hi"\r\nEnd Sub\r\n')
|
|
85
|
+
wb.save() # overwrites the original file
|
|
86
|
+
# wb.save("edited.xlsm") # ...or save to a new file
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
That is the entire core API. Three methods.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Add, rename, or delete a module
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from pyopenvba import ExcelFile, VBAModuleKind
|
|
97
|
+
|
|
98
|
+
with ExcelFile("workbook.xlsm") as wb:
|
|
99
|
+
project = wb.vba_project()
|
|
100
|
+
|
|
101
|
+
project.add_module(
|
|
102
|
+
"NewModule",
|
|
103
|
+
"Sub Hi()\r\n MsgBox \"hi\"\r\nEnd Sub\r\n",
|
|
104
|
+
kind=VBAModuleKind.standard,
|
|
105
|
+
)
|
|
106
|
+
project.rename_module("OldName", "NewName")
|
|
107
|
+
project.delete_module("Obsolete")
|
|
108
|
+
|
|
109
|
+
wb.save("out.xlsm")
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Edit your macros as files on disk (recommended workflow)
|
|
115
|
+
|
|
116
|
+
This is the easiest way to manage VBA in a git repo. Export every
|
|
117
|
+
module to a folder, edit the files in any text editor, then push the
|
|
118
|
+
changes back into the workbook.
|
|
119
|
+
|
|
120
|
+
From the command line:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
# 1. Pull every module out of the workbook into ./vba/
|
|
124
|
+
python -m pyopenvba pull workbook.xlsm ./vba
|
|
125
|
+
|
|
126
|
+
# 2. ...edit ./vba/Module1.bas in your editor of choice...
|
|
127
|
+
|
|
128
|
+
# 3. Push your edits back into the workbook
|
|
129
|
+
python -m pyopenvba push ./vba workbook.xlsm
|
|
130
|
+
|
|
131
|
+
# Bonus: list modules without extracting anything
|
|
132
|
+
python -m pyopenvba ls workbook.xlsm
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
From Python:
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
from pyopenvba import pull, push
|
|
139
|
+
|
|
140
|
+
pull("workbook.xlsm", "./vba")
|
|
141
|
+
# ...edit files...
|
|
142
|
+
push("./vba", "workbook.xlsm") # in place
|
|
143
|
+
push("./vba", "workbook.xlsm", out="edited.xlsm") # to a new file
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Module files use the extensions VBA already uses: `.bas` for standard
|
|
147
|
+
modules, `.cls` for class modules, `.frm` for UserForm code-behind.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Supported formats
|
|
152
|
+
|
|
153
|
+
| Extension | What it is | Read | Write |
|
|
154
|
+
|-----------|------------------------------|:----:|:-----:|
|
|
155
|
+
| `.xlsm` | Macro-enabled workbook | yes | yes |
|
|
156
|
+
| `.xlsb` | Binary workbook | yes | yes |
|
|
157
|
+
| `.xlam` | Macro-enabled add-in | yes | yes |
|
|
158
|
+
| `.xls` | Legacy (Excel 97-2003) | yes | yes |
|
|
159
|
+
|
|
160
|
+
Every save is verified to reopen in Excel **without** the "we found a
|
|
161
|
+
problem with some content" repair dialog.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Safety guards
|
|
166
|
+
|
|
167
|
+
`save()` refuses to silently produce a broken workbook.
|
|
168
|
+
|
|
169
|
+
### Password-protected projects
|
|
170
|
+
|
|
171
|
+
If the VBA project is password-protected, any mutation will raise
|
|
172
|
+
`VBAProjectError` unless you explicitly opt in:
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
wb.save(allow_protected=True)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
The library never tries to decrypt or change the password - it just
|
|
179
|
+
preserves the existing protection bytes verbatim. The resulting
|
|
180
|
+
workbook still requires the original password to open the VBE.
|
|
181
|
+
|
|
182
|
+
### Digitally-signed projects
|
|
183
|
+
|
|
184
|
+
A digital signature is invalidated by *any* change to the macros. On
|
|
185
|
+
mutation, the library drops the stale signature streams and emits a
|
|
186
|
+
`UserWarning` so you know trust has been removed:
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
import warnings
|
|
190
|
+
warnings.filterwarnings("error", category=UserWarning) # treat as fatal
|
|
191
|
+
|
|
192
|
+
# ...or silence the warning if you accept the consequence:
|
|
193
|
+
wb.save(allow_invalidate_signature=True)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## What's out of scope
|
|
199
|
+
|
|
200
|
+
This library is intentionally focused on **module source code**. The
|
|
201
|
+
following are preserved byte-for-byte but not interpreted:
|
|
202
|
+
|
|
203
|
+
- UserForm **layout** (controls, properties, positions). Editing the
|
|
204
|
+
**code-behind** of a UserForm works fine; editing the design surface
|
|
205
|
+
does not.
|
|
206
|
+
- VBA project password decryption / re-encryption.
|
|
207
|
+
- Re-signing digitally signed projects.
|
|
208
|
+
- ActiveX license editing.
|
|
209
|
+
|
|
210
|
+
See [docs/roadmap.md](docs/roadmap.md) for the full feature matrix.
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## Architecture
|
|
215
|
+
|
|
216
|
+
```
|
|
217
|
+
src/pyopenvba/
|
|
218
|
+
__init__.py public API (ExcelFile, pull, push, exceptions)
|
|
219
|
+
excel.py ExcelFile facade (ZIP / CFB dispatch, pull/push helpers)
|
|
220
|
+
vba.py VBA project parser + MS-OVBA codec
|
|
221
|
+
cfb.py MS-CFB (Compound File Binary) parser/writer
|
|
222
|
+
exceptions.py custom exception hierarchy
|
|
223
|
+
__main__.py `python -m pyopenvba {pull,push,ls}` CLI
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
For deeper documentation:
|
|
227
|
+
|
|
228
|
+
- [docs/architecture.md](docs/architecture.md) - internal module layout.
|
|
229
|
+
- [docs/ms-ovba-implementation-guide_v2.md](docs/ms-ovba-implementation-guide_v2.md) -
|
|
230
|
+
language-agnostic guide for re-implementing MS-OVBA in another
|
|
231
|
+
language.
|
|
232
|
+
- [docs/roadmap.md](docs/roadmap.md) - per-feature implementation status.
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Contributing
|
|
237
|
+
|
|
238
|
+
Bug reports, weird workbooks that break the library, and PRs are all
|
|
239
|
+
welcome. Please include the workbook (or a minimal redacted version)
|
|
240
|
+
when filing a parsing bug.
|
|
241
|
+
|
|
242
|
+
Run the test suite:
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
pip install pytest pyright
|
|
246
|
+
pytest
|
|
247
|
+
pyright src tests
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## License
|
|
253
|
+
|
|
254
|
+
[MIT](LICENSE.md).
|