pyOpenVBA 6.1.2__tar.gz → 6.1.3__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-6.1.2 → pyopenvba-6.1.3}/PKG-INFO +9 -2
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/README.md +8 -1
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/docs/architecture.md +10 -6
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/docs/ms-ovba-implementation-guide_v2.md +21 -6
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/pyproject.toml +1 -1
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/__init__.py +1 -1
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/_host.py +42 -10
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/_oforms_pages.py +7 -2
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/_oforms_records.py +25 -1
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/cfb.py +33 -15
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/forms.py +389 -110
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/vba.py +33 -8
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/.gitignore +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/LICENSE.md +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/docs/excel_checklist.csv +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/docs/host_completeness.md +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/docs/power_query.md +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/docs/research/access_complex/README.md +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/docs/research/access_designs/README.md +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/docs/research/access_macros/README.md +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/docs/research/access_write/README.md +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/docs/research/pcode/README.md +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/docs/roadmap.md +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/docs/vba_runtime.md +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/__main__.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/_a1.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/_deflate.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/_new_project.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/_package_signature.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/_ppt_container.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/_references.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/_templates/__init__.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/_templates/blank_files/blank_database.accdb +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/_templates/blank_files/blank_database_module.accdb +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/_templates/blank_files/blank_document.docm +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/_templates/blank_files/blank_excel_addin.xlam +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/_templates/blank_files/blank_presentation.pptm +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/_templates/blank_files/blank_workbook.xlsb +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/_templates/blank_files/blank_workbook.xlsm +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/_templates/blank_files/blank_workbook.xlsx +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/_templates/blank_files/engine_skeleton.accdb +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/_templates/blank_files/engine_skeleton.mdb +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/_templates/designs/form.blob +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/_templates/designs/form.lvprop +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/_templates/designs/form.propdata +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/_templates/designs/form.prototypes +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/_templates/designs/form.typeinfo +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/_templates/designs/report.blob +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/_templates/designs/report.lvprop +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/_templates/designs/report.propdata +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/_templates/designs/report.prototypes +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/_templates/designs/report.typeinfo +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/_xml.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/access/__init__.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/access/_alloc.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/access/_btree.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/access/_collation.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/access/_collation_general_legacy.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/access/_compact.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/access/_complex.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/access/_datapage.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/access/_ddl.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/access/_designs.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/access/_facade.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/access/_format.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/access/_index.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/access/_lval.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/access/_macros.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/access/_pages.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/access/_props.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/access/_queries.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/access/_rows.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/access/_schema.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/access/_sql.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/access/_storage.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/access/_tdef.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/access/_validate.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/access/_vba.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/access/database.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/access_read.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/__init__.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/__init__.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_arrays.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_autofill.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_autofilter.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_bridge.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_calc.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_clipboard.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_control_refs.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_controls.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_copy_names.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_dimensions.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_duplicates.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_dynamic_file.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_editing.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_engine_book.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_evaluate.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_events.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_find.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_font_rows.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_formats.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_formula_format.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_io.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_merges.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_model.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_named_api.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_names.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_notes.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_notes_file.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_number_format.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_protection.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_radios.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_refresh.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_region.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_row_formats.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_shape_api.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_shapes.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_shared.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_sheet_copy.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_sheet_move.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_sort.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_special_cells.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_spills.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_styles.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_table_edits.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_tables.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_typing.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_validation.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_visible.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_windows.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/excel/_worksheet_functions.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/powerpoint/__init__.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/powerpoint/_bridge.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/powerpoint/_io.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/powerpoint/_model.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/word/__init__.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/word/_bridge.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/word/_io.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/apps/word/_model.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/excel.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/exceptions.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/__init__.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_arity.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_calc/__init__.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_calc/catalog.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_calc/cells.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_calc/collate.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_calc/dates.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_calc/evaluator.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_calc/functions/__init__.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_calc/functions/aggregate.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_calc/functions/arithmetic.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_calc/functions/arrays.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_calc/functions/common.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_calc/functions/conditional.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_calc/functions/database.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_calc/functions/dates.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_calc/functions/distributions.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_calc/functions/engineering.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_calc/functions/financial.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_calc/functions/information.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_calc/functions/lambdas.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_calc/functions/logical.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_calc/functions/lookup.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_calc/functions/matrix.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_calc/functions/statistics.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_calc/functions/subtotal.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_calc/functions/text.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_calc/host.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_calc/lexer.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_calc/nodes.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_calc/numbers.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_calc/parser.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_calc/precise.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_calc/reference.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_calc/registry.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_calc/special.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_calc/values.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_deep.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_display.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_formula2.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_inventory.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_parse.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_prefixes.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_r1c1.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_spell.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_structured.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/formula/_values.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/interpreter/__init__.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/interpreter/_ast.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/interpreter/_constants_data.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/interpreter/_intrinsics.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/interpreter/_inventory.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/interpreter/_inventory_data.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/interpreter/_lex.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/interpreter/_objects.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/interpreter/_parse.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/interpreter/_preprocess.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/interpreter/_runtime.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/interpreter/_scripting.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/interpreter/_values.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/mlang/__init__.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/mlang/_eval.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/mlang/_inventory.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/mlang/_inventory_data.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/mlang/_library.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/mlang/_parse.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/mlang/_values.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/powerpoint.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/powerquery/__init__.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/powerquery/_binary.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/powerquery/_files.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/powerquery/_mashup.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/powerquery/_metadata.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/powerquery/_opc.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/powerquery/_package.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/powerquery/_refresh.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/powerquery/_section.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/powerquery/_sheets.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/powerquery/workbook.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/shapes/__init__.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/shapes/_docx.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/shapes/_docx_text.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/shapes/_drawingml.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/shapes/_pptx.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/shapes/_values.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/shapes/_xlsx.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/vba_pcode.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/src/pyopenvba/word.py +0 -0
- {pyopenvba-6.1.2 → pyopenvba-6.1.3}/tests/fuzz_corpus/README.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pyOpenVBA
|
|
3
|
-
Version: 6.1.
|
|
3
|
+
Version: 6.1.3
|
|
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
|
|
@@ -262,6 +262,9 @@ with ExcelFile("workbook.xlsm") as wb:
|
|
|
262
262
|
wb.save("out.xlsm")
|
|
263
263
|
```
|
|
264
264
|
|
|
265
|
+
Deleting a UserForm's module takes the form's design with it, as the
|
|
266
|
+
editor does.
|
|
267
|
+
|
|
265
268
|
```python
|
|
266
269
|
from pyopenvba import AccessDatabase, VBAModuleKind
|
|
267
270
|
|
|
@@ -380,7 +383,10 @@ None)` clears a property, so the control goes back to its default.
|
|
|
380
383
|
MSForms stores a property only when it differs from the control's
|
|
381
384
|
default, so `properties()` returns what the developer set, which no live
|
|
382
385
|
host can tell you. Writing is lossless: an unedited form saves back byte
|
|
383
|
-
for byte.
|
|
386
|
+
for byte. The stored fields are not always named as VBA names them:
|
|
387
|
+
[docs/userforms.md](docs/userforms.md) says where the designers store a
|
|
388
|
+
property that differs, such as `TextAlign`, and what setting one changes
|
|
389
|
+
besides.
|
|
384
390
|
|
|
385
391
|
The command line shows the tree:
|
|
386
392
|
|
|
@@ -1049,6 +1055,7 @@ For more:
|
|
|
1049
1055
|
- [docs/architecture.md](https://github.com/WilliamSmithEdward/pyOpenVBA/blob/main/docs/architecture.md): internal layout and conventions.
|
|
1050
1056
|
- [docs/access_engine.md](https://github.com/WilliamSmithEdward/pyOpenVBA/blob/main/docs/access_engine.md): the Access file format as measured, and what the engine reproduces.
|
|
1051
1057
|
- [docs/power_query.md](https://github.com/WilliamSmithEdward/pyOpenVBA/blob/main/docs/power_query.md): how Excel stores Power Query, and how each rule was measured.
|
|
1058
|
+
- [docs/userforms.md](https://github.com/WilliamSmithEdward/pyOpenVBA/blob/main/docs/userforms.md): where the form designers store the UserForm properties VBA names otherwise, as measured.
|
|
1052
1059
|
- [docs/ms-ovba-implementation-guide_v2.md](https://github.com/WilliamSmithEdward/pyOpenVBA/blob/main/docs/ms-ovba-implementation-guide_v2.md): a language-agnostic guide to re-implementing MS-OVBA.
|
|
1053
1060
|
- [docs/roadmap.md](https://github.com/WilliamSmithEdward/pyOpenVBA/blob/main/docs/roadmap.md): per-feature status.
|
|
1054
1061
|
- [docs/host_completeness.md](docs/host_completeness.md): whole-host completion criteria and the Excel coverage audit; Excel, then Word, then PowerPoint.
|
|
@@ -225,6 +225,9 @@ with ExcelFile("workbook.xlsm") as wb:
|
|
|
225
225
|
wb.save("out.xlsm")
|
|
226
226
|
```
|
|
227
227
|
|
|
228
|
+
Deleting a UserForm's module takes the form's design with it, as the
|
|
229
|
+
editor does.
|
|
230
|
+
|
|
228
231
|
```python
|
|
229
232
|
from pyopenvba import AccessDatabase, VBAModuleKind
|
|
230
233
|
|
|
@@ -343,7 +346,10 @@ None)` clears a property, so the control goes back to its default.
|
|
|
343
346
|
MSForms stores a property only when it differs from the control's
|
|
344
347
|
default, so `properties()` returns what the developer set, which no live
|
|
345
348
|
host can tell you. Writing is lossless: an unedited form saves back byte
|
|
346
|
-
for byte.
|
|
349
|
+
for byte. The stored fields are not always named as VBA names them:
|
|
350
|
+
[docs/userforms.md](docs/userforms.md) says where the designers store a
|
|
351
|
+
property that differs, such as `TextAlign`, and what setting one changes
|
|
352
|
+
besides.
|
|
347
353
|
|
|
348
354
|
The command line shows the tree:
|
|
349
355
|
|
|
@@ -1012,6 +1018,7 @@ For more:
|
|
|
1012
1018
|
- [docs/architecture.md](https://github.com/WilliamSmithEdward/pyOpenVBA/blob/main/docs/architecture.md): internal layout and conventions.
|
|
1013
1019
|
- [docs/access_engine.md](https://github.com/WilliamSmithEdward/pyOpenVBA/blob/main/docs/access_engine.md): the Access file format as measured, and what the engine reproduces.
|
|
1014
1020
|
- [docs/power_query.md](https://github.com/WilliamSmithEdward/pyOpenVBA/blob/main/docs/power_query.md): how Excel stores Power Query, and how each rule was measured.
|
|
1021
|
+
- [docs/userforms.md](https://github.com/WilliamSmithEdward/pyOpenVBA/blob/main/docs/userforms.md): where the form designers store the UserForm properties VBA names otherwise, as measured.
|
|
1015
1022
|
- [docs/ms-ovba-implementation-guide_v2.md](https://github.com/WilliamSmithEdward/pyOpenVBA/blob/main/docs/ms-ovba-implementation-guide_v2.md): a language-agnostic guide to re-implementing MS-OVBA.
|
|
1016
1023
|
- [docs/roadmap.md](https://github.com/WilliamSmithEdward/pyOpenVBA/blob/main/docs/roadmap.md): per-feature status.
|
|
1017
1024
|
- [docs/host_completeness.md](docs/host_completeness.md): whole-host completion criteria and the Excel coverage audit; Excel, then Word, then PowerPoint.
|
|
@@ -345,12 +345,16 @@ written one needs (`scripts/measure_first_macro.py`).
|
|
|
345
345
|
|
|
346
346
|
## 5a. UserForm designer streams
|
|
347
347
|
|
|
348
|
-
A form's design lives beside the VBA storage, not inside it: a
|
|
349
|
-
|
|
350
|
-
what order), `o` (each control's own property
|
|
351
|
-
`VBFrame` text. Containers nest into storages of their
|
|
352
|
-
for the site id -- a `Frame`'s children in `i02`, a
|
|
353
|
-
in `i08` / `i09` under its own `i06`.
|
|
348
|
+
A form's design lives beside the VBA storage, not inside it: a storage
|
|
349
|
+
named for the form in the project's storage, holding `f` (the sites:
|
|
350
|
+
which controls, in what order), `o` (each control's own property
|
|
351
|
+
record), and the `VBFrame` text. Containers nest into storages of their
|
|
352
|
+
own, named for the site id -- a `Frame`'s children in `i02`, a
|
|
353
|
+
`MultiPage`'s Pages in `i08` / `i09` under its own `i06`. The project's
|
|
354
|
+
storage is the root of `vbaProject.bin` and of a `.ppt`'s embedded
|
|
355
|
+
project, and `_VBA_PROJECT_CUR` or `Macros` in a `.xls` or `.doc`, which
|
|
356
|
+
hold the document too (`VBAHostFile._project_root`,
|
|
357
|
+
tests/fixtures/binary_forms.json).
|
|
354
358
|
|
|
355
359
|
`forms.py` reads that tree and writes it back; `_oforms_records.py`
|
|
356
360
|
carries one property table per control class. It never guesses: every
|
|
@@ -466,6 +466,7 @@ Document=ThisWorkbook/&H00000000
|
|
|
466
466
|
Document=Sheet1/&H00000000
|
|
467
467
|
Module=Module1
|
|
468
468
|
Class=Class1
|
|
469
|
+
Package={AC9F2F90-E877-11CE-9F68-00AA00574A4F}
|
|
469
470
|
BaseClass=UserForm1
|
|
470
471
|
Name="VBAProject"
|
|
471
472
|
HelpContextID="0"
|
|
@@ -497,6 +498,15 @@ entries together. Do not create `Document=` lines (host-owned). Do
|
|
|
497
498
|
not create `BaseClass=` lines unless you also emit a designer
|
|
498
499
|
sub-storage.
|
|
499
500
|
|
|
501
|
+
The editor writes `Package={AC9F2F90-E877-11CE-9F68-00AA00574A4F}`,
|
|
502
|
+
the Forms designer's package, once: immediately before the
|
|
503
|
+
`BaseClass=` line of a form added to a stream that has no such line.
|
|
504
|
+
It never moves or removes the line afterwards, even when the last
|
|
505
|
+
form goes, and a declaration added later goes after it as after any
|
|
506
|
+
other. A project that declares a form without the line gets it only
|
|
507
|
+
with its next new form. Keep every line a mutation does not touch
|
|
508
|
+
byte for byte; a `[Workspace]` value can end in a space.
|
|
509
|
+
|
|
500
510
|
---
|
|
501
511
|
|
|
502
512
|
## 9. The `PROJECTwm` stream
|
|
@@ -530,17 +540,20 @@ deprecated control surface.
|
|
|
530
540
|
|
|
531
541
|
## 11. UserForms / designer storages
|
|
532
542
|
|
|
533
|
-
For each `BaseClass=` module there is a **sub-storage**
|
|
534
|
-
with the same name as the module
|
|
543
|
+
For each `BaseClass=` module there is a **sub-storage** in the project's
|
|
544
|
+
storage, beside `/VBA`, with the same name as the module: at the CFB
|
|
545
|
+
root of `vbaProject.bin`, in `_VBA_PROJECT_CUR` in a `.xls` and in
|
|
546
|
+
`Macros` in a `.doc`. It contains designer streams:
|
|
535
547
|
typically `f`, `o`, `\x01CompObj`, `\x03VBFrame`, and sometimes more.
|
|
536
548
|
|
|
537
549
|
Editing the **code-behind** of a UserForm is identical to editing any
|
|
538
550
|
other module's source: the form's *code* lives in `/VBA/<FormName>` and
|
|
539
551
|
is round-tripped through the same compressed-source pipeline.
|
|
540
552
|
|
|
541
|
-
Editing the **layout** (controls, properties, positions)
|
|
542
|
-
Office Forms binary format
|
|
543
|
-
|
|
553
|
+
Editing the **layout** (controls, properties, positions) means writing
|
|
554
|
+
the Office Forms binary format, [MS-OFORMS]. pyOpenVBA reads and writes
|
|
555
|
+
it in `forms.py` (docs/architecture.md, section 5a). A writer that does
|
|
556
|
+
not must preserve the designer sub-storage byte for byte.
|
|
544
557
|
|
|
545
558
|
---
|
|
546
559
|
|
|
@@ -726,10 +739,12 @@ For each operation, every step must succeed or you must abort.
|
|
|
726
739
|
| Drop declaration line | `PROJECT` |
|
|
727
740
|
| Drop `[Workspace]` entry | `PROJECT` |
|
|
728
741
|
| Drop NAMEMAP pair | `PROJECTwm` |
|
|
742
|
+
| Remove a form's designer sub-storage | beside `/VBA` |
|
|
729
743
|
| Invalidate + drop SRP + signatures | as above |
|
|
730
744
|
|
|
731
745
|
Forbidden: deleting `Document=` modules (host-owned), deleting
|
|
732
|
-
designer modules without also removing their sub-storage.
|
|
746
|
+
designer modules without also removing their sub-storage. The
|
|
747
|
+
`Package=` line stays when the last form goes.
|
|
733
748
|
|
|
734
749
|
---
|
|
735
750
|
|
|
@@ -24,7 +24,7 @@ from pyopenvba._new_project import with_project
|
|
|
24
24
|
from pyopenvba._package_signature import signature_parts, without_signature
|
|
25
25
|
from pyopenvba._references import ReferenceManager, module_offset, reference_spans
|
|
26
26
|
from pyopenvba.exceptions import NoVBAProjectError, UnsupportedFormatError, VBAProjectError
|
|
27
|
-
from pyopenvba.forms import VBAForm, create_form, form_names, read_forms
|
|
27
|
+
from pyopenvba.forms import VBAForm, create_form, form_names, read_form, read_forms
|
|
28
28
|
from pyopenvba.vba import (
|
|
29
29
|
SignatureInfo,
|
|
30
30
|
VBAModuleKind,
|
|
@@ -226,7 +226,7 @@ class VBAHostFile(ReferenceManager):
|
|
|
226
226
|
return {"workbook": "excel", "document": "word", "presentation": "powerpoint"}[self._host_noun]
|
|
227
227
|
|
|
228
228
|
def _reference_forms(self) -> list[str]:
|
|
229
|
-
return form_names(self._get_cfb())
|
|
229
|
+
return form_names(self._get_cfb(), root=self._project_root())
|
|
230
230
|
|
|
231
231
|
def references(self) -> list[VBAReference]:
|
|
232
232
|
"""Declared libraries in priority order; none in a file with no project."""
|
|
@@ -265,13 +265,14 @@ class VBAHostFile(ReferenceManager):
|
|
|
265
265
|
streams do not reconcile.
|
|
266
266
|
|
|
267
267
|
The result is cached, so property edits made on it are the ones
|
|
268
|
-
:meth:`save` writes back.
|
|
268
|
+
:meth:`save` writes back. Adding a form and saving keep the same
|
|
269
|
+
objects, and with them the edits made through each.
|
|
269
270
|
"""
|
|
270
271
|
if not self._has_project:
|
|
271
272
|
return []
|
|
272
273
|
if self._forms is None:
|
|
273
274
|
self._forms = read_forms(
|
|
274
|
-
self._get_cfb(), code_page=self.vba_project().code_page
|
|
275
|
+
self._get_cfb(), code_page=self.vba_project().code_page, root=self._project_root()
|
|
275
276
|
)
|
|
276
277
|
return self._forms
|
|
277
278
|
|
|
@@ -294,7 +295,8 @@ class VBAHostFile(ReferenceManager):
|
|
|
294
295
|
``MSForms`` types compiles.
|
|
295
296
|
|
|
296
297
|
The form is returned ready to edit -- ``add_control`` and friends
|
|
297
|
-
work on it straight away -- and lands on disk at :meth:`save`.
|
|
298
|
+
work on it straight away -- and lands on disk at :meth:`save`. The
|
|
299
|
+
forms read or added before it are left as they are, edits included.
|
|
298
300
|
"""
|
|
299
301
|
project = self.vba_project()
|
|
300
302
|
cfb = self._get_cfb()
|
|
@@ -305,13 +307,21 @@ class VBAHostFile(ReferenceManager):
|
|
|
305
307
|
width=width,
|
|
306
308
|
height=height,
|
|
307
309
|
code_page=project.code_page,
|
|
310
|
+
root=self._project_root(),
|
|
308
311
|
)
|
|
309
312
|
project.add_module(name, header, kind=VBAModuleKind.other)
|
|
310
313
|
# The editor declares Microsoft Forms with a project's first form, and code naming its types needs it.
|
|
311
314
|
self._ensure_forms_reference()
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
+
if self._forms is None:
|
|
316
|
+
# Nothing read yet, so nothing edited: reading every form reads this one too.
|
|
317
|
+
return next(f for f in self.forms() if f.name == name)
|
|
318
|
+
# The forms read before keep their objects, and with them any edit made through one and not saved
|
|
319
|
+
# yet: only the new form is read, and the cache keeps the directory order read_forms gives.
|
|
320
|
+
root = self._project_root()
|
|
321
|
+
cached = {form.name: form for form in self._forms}
|
|
322
|
+
cached[name] = read_form(cfb, name, code_page=project.code_page, root=root)
|
|
323
|
+
self._forms = [cached[each] for each in form_names(cfb, root=root)]
|
|
324
|
+
return cached[name]
|
|
315
325
|
|
|
316
326
|
def module_names(self) -> list[str]:
|
|
317
327
|
"""Return the list of VBA module names."""
|
|
@@ -630,7 +640,7 @@ class VBAHostFile(ReferenceManager):
|
|
|
630
640
|
module.dirty = False
|
|
631
641
|
if module.kind == VBAModuleKind.standard:
|
|
632
642
|
decl_key = "Module"
|
|
633
|
-
elif module.name in form_names(cfb):
|
|
643
|
+
elif module.name in form_names(cfb, root=self._project_root()):
|
|
634
644
|
# A designer is declared BaseClass, not Class. The
|
|
635
645
|
# test is structural -- its name is also a storage
|
|
636
646
|
# beside VBA/ -- which is the same one forms.py uses
|
|
@@ -642,12 +652,22 @@ class VBAHostFile(ReferenceManager):
|
|
|
642
652
|
decl_key = "Class"
|
|
643
653
|
add_modules_for_project.append((module.name, decl_key))
|
|
644
654
|
|
|
645
|
-
# 3. Delete streams the user removed in-memory.
|
|
655
|
+
# 3. Delete streams the user removed in-memory. A form goes with its
|
|
656
|
+
# designer storage, as the editor removes one; left behind, the
|
|
657
|
+
# storage still reads as a form that holds its name.
|
|
658
|
+
root = self._project_root()
|
|
659
|
+
forms = {name.casefold(): name for name in form_names(cfb, root=root)}
|
|
646
660
|
for name in sorted(delete_names):
|
|
647
661
|
try:
|
|
648
662
|
cfb.remove_stream_in_storage("VBA", name)
|
|
649
663
|
except KeyError:
|
|
650
664
|
pass
|
|
665
|
+
if name.casefold() in forms:
|
|
666
|
+
cfb.remove_storage_at(root, forms[name.casefold()])
|
|
667
|
+
if self._forms is not None:
|
|
668
|
+
# Only that form leaves the cache: the others keep their objects, so an edit made
|
|
669
|
+
# through one after this save still saves.
|
|
670
|
+
self._forms = [form for form in self._forms if form.name != forms[name.casefold()]]
|
|
651
671
|
|
|
652
672
|
project.pending_renames.clear()
|
|
653
673
|
project.pending_adds.clear()
|
|
@@ -901,3 +921,15 @@ class VBAHostFile(ReferenceManager):
|
|
|
901
921
|
vba_bin = self._zip.read(self._vba_entry)
|
|
902
922
|
self._cfb = CFB.from_bytes(vba_bin)
|
|
903
923
|
return self._cfb
|
|
924
|
+
|
|
925
|
+
def _project_root(self) -> tuple[str, ...]:
|
|
926
|
+
"""The path of the project's storage in the CFB ``_get_cfb`` returns.
|
|
927
|
+
|
|
928
|
+
Empty where that CFB is the project, as ``vbaProject.bin`` and a
|
|
929
|
+
.ppt's embedded project are. A .xls or .doc holds the document
|
|
930
|
+
too, and Excel and Word keep a form's storage in the project's
|
|
931
|
+
storage beside ``VBA/`` (tests/fixtures/binary_forms.json).
|
|
932
|
+
"""
|
|
933
|
+
if self._suffix in self._cfb_formats and self._project_storage is not None:
|
|
934
|
+
return (self._project_storage,)
|
|
935
|
+
return ()
|
|
@@ -106,8 +106,13 @@ def serialize_string_array(entries: list[TabString], encoding: str) -> bytes:
|
|
|
106
106
|
|
|
107
107
|
|
|
108
108
|
def new_tab_string(text: str) -> TabString:
|
|
109
|
-
"""A fresh entry, compressed when every character fits one byte.
|
|
110
|
-
|
|
109
|
+
"""A fresh entry, compressed when every character fits one byte.
|
|
110
|
+
|
|
111
|
+
An empty one is stored with no compression flag, a count of zero and
|
|
112
|
+
nothing else, as the designers store a tab's empty tip, tag and
|
|
113
|
+
accelerator (tests/fixtures/form_designer.json).
|
|
114
|
+
"""
|
|
115
|
+
return TabString(text, bool(text) and all(ord(c) <= 0xFF for c in text))
|
|
111
116
|
|
|
112
117
|
|
|
113
118
|
@dataclass
|
|
@@ -69,6 +69,9 @@ class RecordSpec:
|
|
|
69
69
|
text_props: bool = False
|
|
70
70
|
raw_tail: bool = False
|
|
71
71
|
"""Bytes after TextProps (rgColumnInfo, TabStripTabFlags)."""
|
|
72
|
+
flags: tuple[tuple[int, str, bool], ...] = ()
|
|
73
|
+
"""Mask bits that store a property with no data: (bit, name, the value
|
|
74
|
+
the bit stands for), the property's default when the bit is clear."""
|
|
72
75
|
|
|
73
76
|
|
|
74
77
|
# ---------------------------------------------------------------------------
|
|
@@ -153,6 +156,9 @@ COMMAND_BUTTON_SPEC = RecordSpec(
|
|
|
153
156
|
extra=(ExtraField(3, "Caption", "str"), ExtraField(5, "Size", "size8")),
|
|
154
157
|
stream=((7, "Picture"), (10, "MouseIcon")),
|
|
155
158
|
text_props=True,
|
|
159
|
+
# fTakeFocusOnClick: set when TakeFocusOnClick is False, with no data
|
|
160
|
+
# (tests/fixtures/form_properties.json).
|
|
161
|
+
flags=((9, "TakeFocusOnClick", False),),
|
|
156
162
|
)
|
|
157
163
|
|
|
158
164
|
LABEL_SPEC = RecordSpec(
|
|
@@ -390,6 +396,9 @@ class ParsedRecord:
|
|
|
390
396
|
for extra in self.spec.extra:
|
|
391
397
|
if extra.name == name:
|
|
392
398
|
return extra.bit
|
|
399
|
+
for bit, flag, _ in self.spec.flags:
|
|
400
|
+
if flag == name:
|
|
401
|
+
return bit
|
|
393
402
|
return None
|
|
394
403
|
|
|
395
404
|
def properties(self) -> dict[str, object]:
|
|
@@ -406,6 +415,13 @@ class ParsedRecord:
|
|
|
406
415
|
out[name] = f"<{len(self.pictures[name])} bytes>"
|
|
407
416
|
else:
|
|
408
417
|
out[name] = value
|
|
418
|
+
# A string set since the record was read has no length field until
|
|
419
|
+
# it is serialized, but it is stored all the same.
|
|
420
|
+
for name, stored in self.strings.items():
|
|
421
|
+
out.setdefault(name, stored.text)
|
|
422
|
+
for bit, name, value in self.spec.flags:
|
|
423
|
+
if self.mask & (1 << bit):
|
|
424
|
+
out[name] = value
|
|
409
425
|
out.update(self.sizes)
|
|
410
426
|
for name, blob in self.arrays.items():
|
|
411
427
|
out[name] = f"<{len(blob)} bytes>"
|
|
@@ -417,7 +433,15 @@ class ParsedRecord:
|
|
|
417
433
|
# -- mutation ---------------------------------------------------------
|
|
418
434
|
|
|
419
435
|
def set_value(self, name: str, value: int | None) -> None:
|
|
420
|
-
"""Set or clear a numeric DataBlock field, adjusting the mask.
|
|
436
|
+
"""Set or clear a numeric DataBlock field, adjusting the mask.
|
|
437
|
+
|
|
438
|
+
A flag, a property the mask stores with no data, is set when
|
|
439
|
+
``value`` is the one its bit stands for and cleared otherwise.
|
|
440
|
+
"""
|
|
441
|
+
flag = next(((bit, when) for bit, flag, when in self.spec.flags if flag == name), None)
|
|
442
|
+
if flag is not None:
|
|
443
|
+
self._set_bit(flag[0], value is not None and bool(value) == flag[1])
|
|
444
|
+
return
|
|
421
445
|
spec_field = next(
|
|
422
446
|
(f for f in self.spec.data if f.name == name and f.kind != "marker"),
|
|
423
447
|
None,
|
|
@@ -38,6 +38,11 @@ _FATSECT: int = 0xFFFF_FFFD # sector is part of the FAT
|
|
|
38
38
|
_DIFSECT: int = 0xFFFF_FFFC # sector is part of the DIFAT
|
|
39
39
|
_NOSTREAM: int = 0xFFFF_FFFF # no sibling / no child
|
|
40
40
|
|
|
41
|
+
# FAT sector numbers the header lists, and a 512-byte DIFAT sector's after it
|
|
42
|
+
# (the sector's last four bytes hold the next DIFAT sector's number).
|
|
43
|
+
_HEADER_DIFAT = 109
|
|
44
|
+
_DIFAT_PER_SECTOR = 127
|
|
45
|
+
|
|
41
46
|
# Directory entry object types
|
|
42
47
|
_OBJTYPE_EMPTY: int = 0
|
|
43
48
|
_OBJTYPE_STORAGE: int = 1
|
|
@@ -836,11 +841,12 @@ class CFB:
|
|
|
836
841
|
topology and per-entry metadata (CLSID, state, color, timestamps).
|
|
837
842
|
|
|
838
843
|
Layout, in sector order:
|
|
839
|
-
[mini-stream data] [regular stream data] [directory] [mini-FAT] [FAT]
|
|
844
|
+
[mini-stream data] [regular stream data] [directory] [mini-FAT] [FAT] [DIFAT]
|
|
840
845
|
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
846
|
+
The header lists the first 109 FAT sectors, which cover 109 x 128
|
|
847
|
+
sectors of 512 bytes, about 7.1 MB. A larger file lists the rest in
|
|
848
|
+
DIFAT sectors ([MS-CFB] 2.5): 127 FAT sector numbers each, then the
|
|
849
|
+
number of the next DIFAT sector, the last one ending the chain.
|
|
844
850
|
"""
|
|
845
851
|
SECTOR = 512
|
|
846
852
|
MINI = 64
|
|
@@ -952,22 +958,22 @@ class CFB:
|
|
|
952
958
|
minifat_first = _ENDOFCHAIN
|
|
953
959
|
n_minifat_sectors = 0
|
|
954
960
|
|
|
955
|
-
# ---- 4. Decide how many FAT sectors we need (fixed-point) ----
|
|
961
|
+
# ---- 4. Decide how many FAT and DIFAT sectors we need (fixed-point) ----
|
|
962
|
+
# The FAT maps every sector, its own and the DIFAT's included, and the
|
|
963
|
+
# DIFAT lists the FAT sectors the header has no room for.
|
|
956
964
|
n_data = len(sector_payloads)
|
|
957
965
|
n_fat = 1
|
|
958
966
|
while True:
|
|
959
|
-
|
|
967
|
+
n_difat = -(-max(0, n_fat - _HEADER_DIFAT) // _DIFAT_PER_SECTOR)
|
|
968
|
+
needed = -(-(n_data + n_fat + n_difat) // ENTRIES_PER_SECTOR)
|
|
960
969
|
if needed <= n_fat:
|
|
961
970
|
break
|
|
962
971
|
n_fat = needed
|
|
963
|
-
if n_fat > 109:
|
|
964
|
-
raise CFBError(
|
|
965
|
-
"CFB writer requires DIFAT chain support for files this large; "
|
|
966
|
-
"not implemented."
|
|
967
|
-
)
|
|
968
972
|
|
|
969
973
|
fat_first = n_data
|
|
970
974
|
fat_sector_ids = list(range(fat_first, fat_first + n_fat))
|
|
975
|
+
difat_first = fat_first + n_fat
|
|
976
|
+
difat_sector_ids = list(range(difat_first, difat_first + n_difat))
|
|
971
977
|
|
|
972
978
|
# ---- 5. Build the FAT ----
|
|
973
979
|
fat = [_FREESECT] * (n_fat * ENTRIES_PER_SECTOR)
|
|
@@ -989,14 +995,26 @@ class CFB:
|
|
|
989
995
|
chain(minifat_first, n_minifat_sectors)
|
|
990
996
|
for s in fat_sector_ids:
|
|
991
997
|
fat[s] = _FATSECT
|
|
998
|
+
for s in difat_sector_ids:
|
|
999
|
+
fat[s] = _DIFSECT
|
|
992
1000
|
|
|
993
1001
|
fat_bytes = struct.pack(f"<{len(fat)}I", *fat)
|
|
994
1002
|
for k in range(n_fat):
|
|
995
1003
|
sector_payloads.append(fat_bytes[k * SECTOR:(k + 1) * SECTOR])
|
|
996
1004
|
|
|
1005
|
+
# The FAT sectors past the header's 109, 127 to a DIFAT sector, each
|
|
1006
|
+
# sector ending with the next one's number.
|
|
1007
|
+
rest = fat_sector_ids[_HEADER_DIFAT:]
|
|
1008
|
+
for k in range(n_difat):
|
|
1009
|
+
listed = rest[k * _DIFAT_PER_SECTOR:(k + 1) * _DIFAT_PER_SECTOR]
|
|
1010
|
+
listed += [_FREESECT] * (_DIFAT_PER_SECTOR - len(listed))
|
|
1011
|
+
following = difat_sector_ids[k + 1] if k + 1 < n_difat else _ENDOFCHAIN
|
|
1012
|
+
sector_payloads.append(struct.pack(f"<{_DIFAT_PER_SECTOR + 1}I", *listed, following))
|
|
1013
|
+
|
|
997
1014
|
# ---- 6. Build the header ----
|
|
998
|
-
difat =
|
|
999
|
-
|
|
1015
|
+
difat = fat_sector_ids[:_HEADER_DIFAT]
|
|
1016
|
+
difat += [_FREESECT] * (_HEADER_DIFAT - len(difat))
|
|
1017
|
+
difat_bytes = struct.pack(f"<{_HEADER_DIFAT}I", *difat)
|
|
1000
1018
|
|
|
1001
1019
|
header = struct.pack(
|
|
1002
1020
|
_HEADER_FMT,
|
|
@@ -1015,8 +1033,8 @@ class CFB:
|
|
|
1015
1033
|
CUTOFF, # mini-stream cutoff size
|
|
1016
1034
|
minifat_first, # first mini-FAT sector
|
|
1017
1035
|
n_minifat_sectors, # num mini-FAT sectors
|
|
1018
|
-
_ENDOFCHAIN,
|
|
1019
|
-
|
|
1036
|
+
difat_first if n_difat else _ENDOFCHAIN, # first DIFAT sector
|
|
1037
|
+
n_difat, # num DIFAT sectors
|
|
1020
1038
|
difat_bytes, # 436-byte DIFAT array
|
|
1021
1039
|
)
|
|
1022
1040
|
|