pyOpenVBA 6.2.0__tar.gz → 6.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/PKG-INFO +13 -1
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/README.md +12 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/docs/architecture.md +11 -9
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/docs/research/access_write/README.md +87 -3
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/pyproject.toml +1 -1
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/__init__.py +1 -1
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/access/_designs.py +131 -8
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/access/_macros.py +1 -3
- pyopenvba-6.3.0/src/pyopenvba/access/_storage.py +238 -0
- pyopenvba-6.3.0/src/pyopenvba/access/_themes.py +200 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/access/_vba.py +200 -20
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/access/database.py +398 -135
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/access_read.py +253 -731
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/vba.py +38 -73
- pyopenvba-6.2.0/src/pyopenvba/_templates/designs/form.propdata +0 -0
- pyopenvba-6.2.0/src/pyopenvba/_templates/designs/report.propdata +0 -0
- pyopenvba-6.2.0/src/pyopenvba/access/_storage.py +0 -119
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/.gitignore +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/LICENSE.md +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/docs/excel_checklist.csv +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/docs/host_completeness.md +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/docs/ms-ovba-implementation-guide_v2.md +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/docs/power_query.md +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/docs/research/access_complex/README.md +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/docs/research/access_designs/README.md +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/docs/research/access_macros/README.md +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/docs/research/pcode/README.md +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/docs/roadmap.md +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/docs/vba_runtime.md +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/__main__.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/_a1.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/_deflate.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/_host.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/_new_project.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/_oforms_pages.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/_oforms_records.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/_package_signature.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/_ppt_container.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/_references.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/_relationships.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/_templates/__init__.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/_templates/blank_files/blank_database.accdb +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/_templates/blank_files/blank_database_module.accdb +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/_templates/blank_files/blank_document.docm +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/_templates/blank_files/blank_excel_addin.xlam +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/_templates/blank_files/blank_presentation.pptm +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/_templates/blank_files/blank_workbook.xlsb +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/_templates/blank_files/blank_workbook.xlsm +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/_templates/blank_files/blank_workbook.xlsx +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/_templates/blank_files/engine_skeleton.accdb +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/_templates/blank_files/engine_skeleton.mdb +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/_templates/designs/form.blob +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/_templates/designs/form.lvprop +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/_templates/designs/form.prototypes +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/_templates/designs/form.typeinfo +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/_templates/designs/report.blob +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/_templates/designs/report.lvprop +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/_templates/designs/report.prototypes +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/_templates/designs/report.typeinfo +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/_xml.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/access/__init__.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/access/_alloc.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/access/_btree.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/access/_collation.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/access/_collation_general_legacy.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/access/_compact.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/access/_complex.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/access/_datapage.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/access/_ddl.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/access/_facade.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/access/_format.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/access/_index.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/access/_lval.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/access/_pages.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/access/_props.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/access/_queries.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/access/_rows.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/access/_schema.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/access/_sql.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/access/_tdef.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/access/_validate.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/__init__.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/__init__.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_arrays.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_autofill.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_autofilter.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_bridge.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_calc.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_cell_styles.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_clipboard.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_control_refs.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_controls.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_copy_names.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_dimensions.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_duplicates.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_dynamic_file.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_editing.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_engine_book.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_evaluate.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_events.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_find.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_font_rows.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_formats.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_formula_format.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_hyperlinks.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_hyperlinks_file.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_io.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_merges.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_model.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_named_api.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_names.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_notes.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_notes_file.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_number_format.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_protection.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_radios.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_refresh.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_region.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_row_formats.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_shape_api.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_shapes.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_shared.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_sheet_copy.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_sheet_move.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_sort.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_special_cells.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_spills.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_styles.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_table_edits.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_tables.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_text_columns.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_typing.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_validation.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_visible.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_windows.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/excel/_worksheet_functions.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/powerpoint/__init__.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/powerpoint/_bridge.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/powerpoint/_io.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/powerpoint/_model.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/word/__init__.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/word/_bridge.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/word/_io.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/apps/word/_model.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/cfb.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/excel.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/exceptions.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/forms.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/__init__.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_arity.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_calc/__init__.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_calc/catalog.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_calc/cells.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_calc/collate.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_calc/dates.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_calc/evaluator.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_calc/functions/__init__.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_calc/functions/aggregate.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_calc/functions/arithmetic.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_calc/functions/arrays.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_calc/functions/common.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_calc/functions/conditional.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_calc/functions/database.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_calc/functions/dates.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_calc/functions/distributions.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_calc/functions/engineering.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_calc/functions/financial.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_calc/functions/information.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_calc/functions/lambdas.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_calc/functions/logical.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_calc/functions/lookup.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_calc/functions/matrix.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_calc/functions/statistics.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_calc/functions/subtotal.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_calc/functions/text.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_calc/host.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_calc/lexer.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_calc/nodes.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_calc/numbers.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_calc/parser.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_calc/precise.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_calc/reference.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_calc/registry.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_calc/special.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_calc/values.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_deep.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_display.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_formula2.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_inventory.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_parse.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_prefixes.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_r1c1.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_spell.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_structured.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/formula/_values.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/interpreter/__init__.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/interpreter/_ast.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/interpreter/_constants_data.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/interpreter/_intrinsics.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/interpreter/_inventory.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/interpreter/_inventory_data.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/interpreter/_lex.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/interpreter/_objects.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/interpreter/_parse.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/interpreter/_preprocess.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/interpreter/_runtime.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/interpreter/_scripting.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/interpreter/_values.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/mlang/__init__.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/mlang/_eval.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/mlang/_inventory.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/mlang/_inventory_data.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/mlang/_library.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/mlang/_parse.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/mlang/_values.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/powerpoint.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/powerquery/__init__.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/powerquery/_binary.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/powerquery/_files.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/powerquery/_mashup.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/powerquery/_metadata.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/powerquery/_opc.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/powerquery/_package.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/powerquery/_refresh.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/powerquery/_section.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/powerquery/_sheets.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/powerquery/workbook.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/shapes/__init__.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/shapes/_docx.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/shapes/_docx_text.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/shapes/_drawingml.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/shapes/_pptx.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/shapes/_values.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/shapes/_xlsx.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/vba_pcode.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/src/pyopenvba/word.py +0 -0
- {pyopenvba-6.2.0 → pyopenvba-6.3.0}/tests/fuzz_corpus/README.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pyOpenVBA
|
|
3
|
-
Version: 6.
|
|
3
|
+
Version: 6.3.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
|
|
@@ -936,6 +936,18 @@ A project its application would not write is not written here either:
|
|
|
936
936
|
Excel writes only the code names for one with no code in it, and
|
|
937
937
|
PowerPoint nothing for one with no module.
|
|
938
938
|
|
|
939
|
+
An Access database answers the same way until its first module, since
|
|
940
|
+
Access makes no project for a database that has never held code.
|
|
941
|
+
`AccessDatabase.add_vba_project()` gives it the one Access makes:
|
|
942
|
+
|
|
943
|
+
```python
|
|
944
|
+
with AccessDatabase("orders.accdb") as db:
|
|
945
|
+
if not db.has_vba_project():
|
|
946
|
+
db.add_vba_project()
|
|
947
|
+
db.add_module("Module1", "Public Function Ping() As Long\r\n Ping = 1\r\nEnd Function")
|
|
948
|
+
db.save()
|
|
949
|
+
```
|
|
950
|
+
|
|
939
951
|
### Access
|
|
940
952
|
|
|
941
953
|
| Extension | What it is | Read | Write | create_new |
|
|
@@ -899,6 +899,18 @@ A project its application would not write is not written here either:
|
|
|
899
899
|
Excel writes only the code names for one with no code in it, and
|
|
900
900
|
PowerPoint nothing for one with no module.
|
|
901
901
|
|
|
902
|
+
An Access database answers the same way until its first module, since
|
|
903
|
+
Access makes no project for a database that has never held code.
|
|
904
|
+
`AccessDatabase.add_vba_project()` gives it the one Access makes:
|
|
905
|
+
|
|
906
|
+
```python
|
|
907
|
+
with AccessDatabase("orders.accdb") as db:
|
|
908
|
+
if not db.has_vba_project():
|
|
909
|
+
db.add_vba_project()
|
|
910
|
+
db.add_module("Module1", "Public Function Ping() As Long\r\n Ping = 1\r\nEnd Function")
|
|
911
|
+
db.save()
|
|
912
|
+
```
|
|
913
|
+
|
|
902
914
|
### Access
|
|
903
915
|
|
|
904
916
|
| Extension | What it is | Read | Write | create_new |
|
|
@@ -230,7 +230,8 @@ follows and how it was measured is in
|
|
|
230
230
|
[docs/access_engine.md](access_engine.md).
|
|
231
231
|
|
|
232
232
|
`AccessDatabase` exposes the same surface as the other hosts:
|
|
233
|
-
`
|
|
233
|
+
`has_vba_project()` and `add_vba_project()` for a database that has
|
|
234
|
+
never held code, `module_names()`, `get_module()`, `set_module()`, `vba_project()` with
|
|
234
235
|
`add_module` / `rename_module` / `delete_module`, `pull_modules()` /
|
|
235
236
|
`push_modules()`, `forms()` and `add_form()` with `add_control` /
|
|
236
237
|
`remove_control` / `set_property` on the result, `save()`, `create_new()`.
|
|
@@ -246,11 +247,12 @@ the project from the module streams on the next open, as Access's own
|
|
|
246
247
|
`/decompile` does. The code has to compile; a live gate has Access run
|
|
247
248
|
what was written and compares the value the code returns.
|
|
248
249
|
|
|
249
|
-
`AccessReader` predates the engine
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
`
|
|
250
|
+
`AccessReader` predates the engine and now reads through it: modules,
|
|
251
|
+
the project's streams and `MSysObjects` come from the rows the engine
|
|
252
|
+
finds them in, and text is decoded in the project's code page. It keeps
|
|
253
|
+
its read API (`get_module`, `vba_modules`, `read_project_info`,
|
|
254
|
+
`identifiers`, `disassemble_module`, the catalog readers,
|
|
255
|
+
`pull_modules`) and `pull_access` still uses it; it writes nothing. The chronicle of the
|
|
254
256
|
attempts that came before the engine is
|
|
255
257
|
[docs/msaccess_lessons_learned.md](msaccess_lessons_learned.md).
|
|
256
258
|
|
|
@@ -488,9 +490,9 @@ tests/
|
|
|
488
490
|
test_powerpoint.py PowerPointFile facade, end-to-end
|
|
489
491
|
test_pull_push.py Disk workflow
|
|
490
492
|
test_gates.py Per-roadmap-gate regression tests
|
|
491
|
-
test_access.py AccessReader read path:
|
|
492
|
-
|
|
493
|
-
byte-for-byte oracle parity
|
|
493
|
+
test_access.py AccessReader read path: modules through
|
|
494
|
+
MSysAccessStorage, code pages,
|
|
495
|
+
byte-for-byte oracle parity
|
|
494
496
|
test_access_engine.py Storage engine read layer against the
|
|
495
497
|
Access-authored fixtures
|
|
496
498
|
test_live_access_engine_gate.py
|
|
@@ -720,11 +720,18 @@ What it writes:
|
|
|
720
720
|
`Modules`, a 13-byte `PropData` under it, and the module's stream under
|
|
721
721
|
`VBA` with a row name of 28 random capitals
|
|
722
722
|
* an entry in `Modules/PropData`, the list of those folders:
|
|
723
|
-
`05 09 02 <folder name, one UTF-16 character> "CB0"`, eleven bytes each
|
|
723
|
+
`05 09 02 <folder name, one UTF-16 character> "CB0"`, eleven bytes each.
|
|
724
|
+
The `09` and `02` are sizes, of the rest of the entry and of the name,
|
|
725
|
+
so a two-digit folder is `05 0b 04 "10" "CB0"` (GitHub issue #34).
|
|
726
|
+
*2026-09-27:* Access writes this entry, and the module's
|
|
727
|
+
`MSysNavPaneGroupToObjects` row, the next time it opens the database,
|
|
728
|
+
not when it adds the module, so the library now leaves both to it
|
|
729
|
+
(`tests/live_access_test/first_module_reopened.accdb`)
|
|
724
730
|
* a dir block of eleven records and PROJECTMODULES up by one
|
|
725
731
|
* entries in DirData, PROJECTwm and PROJECT
|
|
726
732
|
* an `MSysObjects` row of type -32761, a `MSysNavPaneObjectIDs` row and a
|
|
727
|
-
`MSysNavPaneGroupToObjects` row in group 8
|
|
733
|
+
`MSysNavPaneGroupToObjects` row in group 8 (the last, since 2026-09-27,
|
|
734
|
+
left to Access, which adds it on its next open)
|
|
728
735
|
* the `Version` word of `_VBA_PROJECT` set to a value VBA does not know
|
|
729
736
|
|
|
730
737
|
It works on the shipped blank template too, which an earlier note had
|
|
@@ -733,6 +740,20 @@ all it needed.
|
|
|
733
740
|
|
|
734
741
|
### Three allocations that are computed, not chosen
|
|
735
742
|
|
|
743
|
+
> **Corrected 2026-09-27.** The folder base of `4` and the id step of four
|
|
744
|
+
> below were measured with Access driven through pyvbaharness, which
|
|
745
|
+
> injects three modules of its own (`PyVbaHarnessRunner`,
|
|
746
|
+
> `PyVbaHarnessCall`, `PyVbaUserCode`) into the project it runs code in.
|
|
747
|
+
> While they exist they hold folders `1`, `2`, `3` and the next three ids,
|
|
748
|
+
> so the module Access added took `4` and an id four higher; the `{0, 5}`
|
|
749
|
+
> case reusing `4` is the same effect. Access driven over COM alone gives
|
|
750
|
+
> the lowest free folder counting from `0` and the next id
|
|
751
|
+
> (`tests/live_access_test/modules_added.accdb`), the library now does
|
|
752
|
+
> the same, and the live gate lists and runs modules in folders `1` and
|
|
753
|
+
> up. The `AllModules(i).Name` failures that seemed to confirm the old
|
|
754
|
+
> rules came from `\x03DirData` naming folder `4` for every module, which
|
|
755
|
+
> was fixed the same day.
|
|
756
|
+
|
|
736
757
|
Both were found the same way -- a created module that the VBE listed and
|
|
737
758
|
ran, while `CurrentProject.AllModules(i).Name` failed with "refers to an
|
|
738
759
|
object that is closed or doesn't exist".
|
|
@@ -743,7 +764,10 @@ not folders>)`. `Modules` holds four such rows -- `PropData`,
|
|
|
743
764
|
`PropDataCopy`, `DirData` and `DirDataCopy` -- so its folders start at
|
|
744
765
|
`4`, which is why the second module in a database gets `4` and never `1`.
|
|
745
766
|
Only the computed name works: on the blank template `1`, `9` and `A` all
|
|
746
|
-
fail while `4` succeeds.
|
|
767
|
+
fail while `4` succeeds. Past `9` the name is the number in decimal,
|
|
768
|
+
`10`, not the character after `9`: Access filled every container of the
|
|
769
|
+
template past folder 9 and named them `10`, `11` and `12` (GitHub issue
|
|
770
|
+
#34, `tests/live_access_test/folders_past_nine.accdb`).
|
|
747
771
|
|
|
748
772
|
Two rules fit the first four measurements and only one fits all six. A
|
|
749
773
|
count, `chr(0x30 + children - 1)`, works while folders stay contiguous.
|
|
@@ -765,6 +789,66 @@ next id: the step is what an object reserves, not a global stride.
|
|
|
765
789
|
|
|
766
790
|
That last line is the whole trick, and it is why the list is short.
|
|
767
791
|
|
|
792
|
+
### The order of `\x03DirData` and `PropData`: an `std::unordered_map`
|
|
793
|
+
|
|
794
|
+
Access does not append a new object's entry. Adding `Macro1` to
|
|
795
|
+
`Macro11` gave `Macro1`, `Macro10`, `Macro2`, `Macro11`, `Macro3` and on
|
|
796
|
+
to `Macro9`, in one session or in eleven. Forms named `Macro1` to
|
|
797
|
+
`Macro11` come out the same way and macros named `Form1` to `Form11` in
|
|
798
|
+
order, so the order follows the names. Adding `Mod2` to `Mod13` one
|
|
799
|
+
session each, with a copy of the file after every session, showed
|
|
800
|
+
entries that the session never touched changing places:
|
|
801
|
+
|
|
802
|
+
```
|
|
803
|
+
after Mod5 Module1 Mod2 Mod3 Mod4 Mod5
|
|
804
|
+
after Mod6 Mod6 Module1 Mod2 Mod3 Mod4 Mod5
|
|
805
|
+
after Mod7 Module1 Mod6 Mod2 Mod3 Mod4 Mod5 Mod7
|
|
806
|
+
after Mod11 Module1 Mod6 Mod11 Mod2 Mod3 Mod4 Mod5 Mod7 Mod8 Mod9 Mod10
|
|
807
|
+
after Mod13 Module1 Mod6 Mod11 Mod2 Mod12 Mod3 Mod13 Mod4 Mod5 Mod7 Mod8 Mod9 Mod10
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
Guessing the hash from orders like these went nowhere, so the answer
|
|
811
|
+
came from `MSACCESS.EXE`. The storage names sit in a table beside a
|
|
812
|
+
stream class whose reader inserts each entry it reads into a container
|
|
813
|
+
that guards itself with "unordered_map/set too long". A container's
|
|
814
|
+
`\x03DirData` is an MSVC `std::unordered_map` keyed by object name, its
|
|
815
|
+
`PropData` folder list another keyed by folder name, and each stream is
|
|
816
|
+
the map's own order. From the code, and checked against every probe:
|
|
817
|
+
|
|
818
|
+
- **The hash** (at `0x14006c060`) is 16 bits: for each character that
|
|
819
|
+
counts, `h = (h << 5) + (h >> 13) + 1 + (c & 0x1f)`. So case does not
|
|
820
|
+
matter and `0` hashes as `P`. A byte table decides what counts among
|
|
821
|
+
ASCII characters: `"`, `'`, `~` and control characters are skipped,
|
|
822
|
+
and a space or tab counts as 0. A leading `.` is skipped. From the
|
|
823
|
+
first character past ASCII, the rest of the name goes through the
|
|
824
|
+
system code page (`WideCharToMultiByte`, no best fit) and
|
|
825
|
+
`LCMapStringA` upper-casing, a byte at a time, every byte above 1
|
|
826
|
+
counting. A Turkish UI turns two bytes into `I`, which is not
|
|
827
|
+
reproduced.
|
|
828
|
+
- **The map** starts with 8 buckets; a key's bucket is `hash & (buckets
|
|
829
|
+
- 1)`. A new key goes in front of the first key of its bucket, or at
|
|
830
|
+
the end when its bucket is empty. An insert that would leave more keys
|
|
831
|
+
than buckets first grows the map to a power of two at least the new
|
|
832
|
+
size, and eightfold while it is under 512 buckets. The rehash walks the
|
|
833
|
+
list and moves each key to the front of its new bucket.
|
|
834
|
+
- **Loading** inserts the stored entries in their order. That is why a
|
|
835
|
+
session reverses some pairs and not others: the ninth entry grows the
|
|
836
|
+
map, so the first eight are reversed twice and the rest once.
|
|
837
|
+
- **Writing** happens only when the map changed. A delete erases; a
|
|
838
|
+
rename erases and inserts, in both lists under `DoCmd.Rename`, while a
|
|
839
|
+
module renamed in the VBE leaves `PropData` alone. A new object's
|
|
840
|
+
`PropData` line waits for the next open, when the missing lines are
|
|
841
|
+
inserted in DirData's order.
|
|
842
|
+
|
|
843
|
+
Checked on 13 histories, every snapshot included: 36 one-character names
|
|
844
|
+
made in one session forwards and backwards and then reopened, `Mod2` to
|
|
845
|
+
`Mod13` one session each, `Macro1` to `Macro11` both ways, the forms and
|
|
846
|
+
reports, `modules_added.accdb` and three small fixtures. Also on a
|
|
847
|
+
delete, two renames, seven names past ASCII and four with skipped
|
|
848
|
+
characters, and on `_container_order.ps1`'s delete and rename of
|
|
849
|
+
modules, a form and a report in copies of `folders_past_nine.accdb`.
|
|
850
|
+
The library writes these orders (`_storage.access_order`).
|
|
851
|
+
|
|
768
852
|
### `_VBA_PROJECT` is a cache, so invalidate it instead of forging it
|
|
769
853
|
|
|
770
854
|
*This is what shipped; see `pyopenvba/access/_vba.py`.*
|
|
@@ -35,10 +35,11 @@ import re
|
|
|
35
35
|
import struct
|
|
36
36
|
import unicodedata
|
|
37
37
|
from collections.abc import Iterable, Mapping, Sequence
|
|
38
|
-
from dataclasses import dataclass, field
|
|
38
|
+
from dataclasses import dataclass, field, replace
|
|
39
39
|
from pathlib import Path
|
|
40
40
|
from typing import NamedTuple
|
|
41
41
|
|
|
42
|
+
from pyopenvba.access._themes import OFFICE_2007, Theme, theme_color
|
|
42
43
|
from pyopenvba.access_read import AccessError
|
|
43
44
|
|
|
44
45
|
#: `<u32 id><u16 code><u32 type><u32 width><u32 length>`.
|
|
@@ -107,6 +108,11 @@ NAME_CODES = (20, 21)
|
|
|
107
108
|
|
|
108
109
|
#: Where the captured designs live, one folder stream each.
|
|
109
110
|
TEMPLATES = Path(__file__).parents[1] / "_templates" / "designs"
|
|
111
|
+
#: A new form's or report's own `PropData` as Access writes it when it
|
|
112
|
+
#: makes one: property 1, the flag `set_design_code` sets, and nothing
|
|
113
|
+
#: else. A design saved again in a later session gains property 0 as
|
|
114
|
+
#: well, which is how the captured designs carried it.
|
|
115
|
+
NEW_DESIGN_PROP_DATA = bytes.fromhex("00000000" "02" "01000000" "00000000")
|
|
110
116
|
#: The record that carries a design's GUID, which the catalog row repeats.
|
|
111
117
|
GUID_RECORD = 208
|
|
112
118
|
GUID_LENGTH = 16
|
|
@@ -2180,21 +2186,134 @@ _PROTOTYPES: dict[str, dict[int, tuple[DesignRecord, ...]]] = {}
|
|
|
2180
2186
|
DEFAULTS_FREE = frozenset({118})
|
|
2181
2187
|
|
|
2182
2188
|
|
|
2183
|
-
def prototype_records(kind: str, type_code: int) -> tuple[DesignRecord, ...]:
|
|
2189
|
+
def prototype_records(kind: str, type_code: int, theme: Theme = OFFICE_2007) -> tuple[DesignRecord, ...]:
|
|
2184
2190
|
"""The records of the control-defaults object for `type_code` on a
|
|
2185
|
-
design of `kind
|
|
2191
|
+
design of `kind`, as `theme` gives them; refused for a type the
|
|
2192
|
+
fixture did not hold."""
|
|
2186
2193
|
if kind not in _PROTOTYPES:
|
|
2187
2194
|
_header, objects, _trailer = parse_design(template(kind, "prototypes"))
|
|
2188
2195
|
_PROTOTYPES[kind] = {o.type: o.records for o in objects[1:] if o.type is not None}
|
|
2189
2196
|
try:
|
|
2190
|
-
|
|
2197
|
+
records = _PROTOTYPES[kind][type_code]
|
|
2191
2198
|
except KeyError:
|
|
2192
2199
|
name = CONTROL_TYPES.get(type_code, type_code)
|
|
2193
2200
|
raise AccessError(f"no control defaults were captured for a {name} on a {kind}") from None
|
|
2201
|
+
type_name = CONTROL_TYPES.get(type_code, "")
|
|
2202
|
+
measured = PROPERTY_SLOTS.get(type_name, {}).get("TextFontFamily")
|
|
2203
|
+
return rethemed(records, theme, measured[0] if measured else FAMILY_IDS.get(type_name))
|
|
2204
|
+
|
|
2205
|
+
|
|
2206
|
+
# --- what a design takes from its database's theme -------------------------------
|
|
2207
|
+
# The templates and the control defaults were captured in a database on
|
|
2208
|
+
# Office's 2007 theme. Access builds the same objects from whatever theme
|
|
2209
|
+
# the database holds (`_themes`), and the differences are these, measured
|
|
2210
|
+
# by having Access make one form and one report with a control of every
|
|
2211
|
+
# type in a database on each Office theme:
|
|
2212
|
+
#
|
|
2213
|
+
# * a font comes from the theme by its ThemeFontIndex, 0 the heading face
|
|
2214
|
+
# and 1 the body face, with the face's family byte beside it
|
|
2215
|
+
# (TextFontFamily) unless it is the default a sans face takes;
|
|
2216
|
+
# * a colour is worked out again from the slot, tint and shade beside it;
|
|
2217
|
+
# * the design itself carries the body face, its family byte and the
|
|
2218
|
+
# theme's Background 2 colour.
|
|
2219
|
+
#
|
|
2220
|
+
# Access also sizes a tab control's strip and pages by the face's metrics,
|
|
2221
|
+
# which is not reproduced here.
|
|
2222
|
+
|
|
2223
|
+
#: A defaults object's family-byte slot where the measured slots have none.
|
|
2224
|
+
FAMILY_IDS = {"NavigationButton": 368}
|
|
2225
|
+
THEME_FONT_INDEX = PROPERTY_CODES["ThemeFontIndex"]
|
|
2226
|
+
FONT_NAME = PROPERTY_CODES["FontName"]
|
|
2227
|
+
TEXT_FONT_FAMILY = PROPERTY_CODES["TextFontFamily"]
|
|
2228
|
+
#: The design's own face, its family byte's slot, and its Background 2.
|
|
2229
|
+
DESIGN_FONT = 160
|
|
2230
|
+
DESIGN_FONT_FAMILY_SLOT = (56, 244, 2, 1)
|
|
2231
|
+
DESIGN_BACKGROUND = 319
|
|
2232
|
+
BACKGROUND_2 = 3
|
|
2233
|
+
#: Each themed colour beside the slot, tint and shade it is worked out from.
|
|
2234
|
+
THEMED_COLORS = tuple(
|
|
2235
|
+
(
|
|
2236
|
+
PROPERTY_CODES[f"{part}Color"],
|
|
2237
|
+
PROPERTY_CODES[f"{part}ThemeColorIndex"],
|
|
2238
|
+
PROPERTY_CODES.get(f"{part}Tint"),
|
|
2239
|
+
PROPERTY_CODES.get(f"{part}Shade"),
|
|
2240
|
+
)
|
|
2241
|
+
for part in ("Back", "Border", "Fore", "Gridline", "Hover", "Pressed", "HoverFore", "PressedFore")
|
|
2242
|
+
)
|
|
2243
|
+
|
|
2244
|
+
|
|
2245
|
+
def _signed(record: DesignRecord) -> int:
|
|
2246
|
+
return int.from_bytes(record.value, "little", signed=True)
|
|
2247
|
+
|
|
2248
|
+
|
|
2249
|
+
def _themed_face(
|
|
2250
|
+
out: dict[int, DesignRecord], face: str, family: DesignRecord | None, family_slot: tuple[int, ...] | None
|
|
2251
|
+
) -> None:
|
|
2252
|
+
"""Set the family byte `face` takes: none for the default, else the
|
|
2253
|
+
record at `family_slot`, `(id, code, value type, width)`."""
|
|
2254
|
+
byte = FONT_FAMILIES.get(face)
|
|
2255
|
+
if byte is None:
|
|
2256
|
+
if family is not None:
|
|
2257
|
+
del out[family.id]
|
|
2258
|
+
elif family is not None:
|
|
2259
|
+
out[family.id] = replace(family, value=bytes((byte,)))
|
|
2260
|
+
elif family_slot is not None:
|
|
2261
|
+
slot_id, code, value_type, width = family_slot[:4]
|
|
2262
|
+
out[slot_id] = DesignRecord(slot_id, code, value_type, width, bytes((byte,)))
|
|
2263
|
+
|
|
2264
|
+
|
|
2265
|
+
def rethemed(records: Sequence[DesignRecord], theme: Theme, family_id: int | None) -> tuple[DesignRecord, ...]:
|
|
2266
|
+
"""Records captured on Office's 2007 theme, as `theme` gives them.
|
|
2267
|
+
`family_id` is where the object's family byte goes when the theme's
|
|
2268
|
+
face needs one and the object has none."""
|
|
2269
|
+
if theme == OFFICE_2007:
|
|
2270
|
+
return tuple(records)
|
|
2271
|
+
by_code = {r.code: r for r in records}
|
|
2272
|
+
out = {r.id: r for r in records}
|
|
2273
|
+
index = by_code.get(THEME_FONT_INDEX)
|
|
2274
|
+
font = by_code.get(FONT_NAME)
|
|
2275
|
+
if index is not None and font is not None and _signed(index) in (0, 1):
|
|
2276
|
+
face = theme.major_font if _signed(index) == 0 else theme.minor_font
|
|
2277
|
+
out[font.id] = replace(font, value=face.encode("utf-16-le"))
|
|
2278
|
+
slot = None if family_id is None else (family_id, TEXT_FONT_FAMILY, 2, 1)
|
|
2279
|
+
_themed_face(out, face, by_code.get(TEXT_FONT_FAMILY), slot)
|
|
2280
|
+
for color, slot_code, tint_code, shade_code in THEMED_COLORS:
|
|
2281
|
+
found, slot_record = by_code.get(color), by_code.get(slot_code)
|
|
2282
|
+
if found is None or slot_record is None or _signed(slot_record) < 0:
|
|
2283
|
+
continue
|
|
2284
|
+
tint = by_code.get(tint_code) if tint_code is not None else None
|
|
2285
|
+
shade = by_code.get(shade_code) if shade_code is not None else None
|
|
2286
|
+
out[found.id] = replace(found, value=theme_color(
|
|
2287
|
+
theme,
|
|
2288
|
+
_signed(slot_record),
|
|
2289
|
+
struct.unpack("<f", tint.value)[0] if tint is not None else 100.0,
|
|
2290
|
+
struct.unpack("<f", shade.value)[0] if shade is not None else 100.0,
|
|
2291
|
+
))
|
|
2292
|
+
return tuple(out[record_id] for record_id in sorted(out))
|
|
2293
|
+
|
|
2294
|
+
|
|
2295
|
+
def rethemed_design(blob: bytes, theme: Theme) -> bytes:
|
|
2296
|
+
"""An empty design from the templates, as `theme` gives it."""
|
|
2297
|
+
if theme == OFFICE_2007:
|
|
2298
|
+
return blob
|
|
2299
|
+
header, objects, trailer = parse_design(blob)
|
|
2300
|
+
own = {r.id: r for r in objects[0].records}
|
|
2301
|
+
by_code = {r.code: r for r in objects[0].records}
|
|
2302
|
+
font = by_code.get(DESIGN_FONT)
|
|
2303
|
+
if font is not None:
|
|
2304
|
+
own[font.id] = replace(font, value=theme.minor_font.encode("utf-16-le"))
|
|
2305
|
+
background = by_code.get(DESIGN_BACKGROUND)
|
|
2306
|
+
if background is not None:
|
|
2307
|
+
own[background.id] = replace(background, value=theme_color(theme, BACKGROUND_2))
|
|
2308
|
+
family = next((r for r in objects[0].records if r.code == DESIGN_FONT_FAMILY_SLOT[1]), None)
|
|
2309
|
+
_themed_face(own, theme.minor_font, family, DESIGN_FONT_FAMILY_SLOT)
|
|
2310
|
+
design = replace(objects[0], records=tuple(own[record_id] for record_id in sorted(own)))
|
|
2311
|
+
rest = tuple(replace(o, records=rethemed(o.records, theme, None)) for o in objects[1:])
|
|
2312
|
+
return build_design(header, (design, *rest), trailer)
|
|
2194
2313
|
|
|
2195
2314
|
|
|
2196
2315
|
def with_prototype(
|
|
2197
|
-
objects: tuple[DesignObject, ...], kind: str, type_code: int
|
|
2316
|
+
objects: tuple[DesignObject, ...], kind: str, type_code: int, theme: Theme = OFFICE_2007
|
|
2198
2317
|
) -> tuple[DesignObject, ...]:
|
|
2199
2318
|
"""The design with the control-defaults object for `type_code` in
|
|
2200
2319
|
place: inserted in type order among the ones there, the run of
|
|
@@ -2203,7 +2322,7 @@ def with_prototype(
|
|
|
2203
2322
|
ahead = list(objects[1:first_section])
|
|
2204
2323
|
if type_code in DEFAULTS_FREE or any(o.type == type_code for o in ahead):
|
|
2205
2324
|
return objects
|
|
2206
|
-
fresh = DesignObject(None, type_code, None, prototype_records(kind, type_code))
|
|
2325
|
+
fresh = DesignObject(None, type_code, None, prototype_records(kind, type_code, theme))
|
|
2207
2326
|
at = next((i for i, o in enumerate(ahead) if (o.type or 0) > type_code), len(ahead))
|
|
2208
2327
|
ahead.insert(at, fresh)
|
|
2209
2328
|
sections = [(i, o) for i, o in enumerate(objects) if o.is_section]
|
|
@@ -2296,6 +2415,8 @@ BUTTON_FILL_COMPANIONS: tuple[tuple[str, object], ...] = (("Gradient", 0),)
|
|
|
2296
2415
|
#: from the installed font; these are the values it wrote for the fonts
|
|
2297
2416
|
#: below (measured on 24 fonts), and a font not listed gets its name alone.
|
|
2298
2417
|
FONT_FAMILIES: dict[str, int] = {
|
|
2418
|
+
"Aptos": 0,
|
|
2419
|
+
"Aptos Display": 0,
|
|
2299
2420
|
"Book Antiqua": 18,
|
|
2300
2421
|
"Cambria": 18,
|
|
2301
2422
|
"Comic Sans MS": 66,
|
|
@@ -2467,6 +2588,7 @@ def add_control(
|
|
|
2467
2588
|
height: int = 240,
|
|
2468
2589
|
caption: str | None = None,
|
|
2469
2590
|
kind: str = "form",
|
|
2591
|
+
theme: Theme = OFFICE_2007,
|
|
2470
2592
|
) -> bytes:
|
|
2471
2593
|
"""A design with one more control on it.
|
|
2472
2594
|
|
|
@@ -2480,7 +2602,8 @@ def add_control(
|
|
|
2480
2602
|
the section's.
|
|
2481
2603
|
|
|
2482
2604
|
The first control of a type also brings the type's control-defaults
|
|
2483
|
-
object ahead of the sections, as it does in Access (`with_prototype`)
|
|
2605
|
+
object ahead of the sections, as it does in Access (`with_prototype`),
|
|
2606
|
+
in the fonts and colours of `theme`, the database's.
|
|
2484
2607
|
"""
|
|
2485
2608
|
header, objects, trailer = parse_design(blob)
|
|
2486
2609
|
if any(o.name == name for o in objects):
|
|
@@ -2512,7 +2635,7 @@ def add_control(
|
|
|
2512
2635
|
)
|
|
2513
2636
|
# The first control of a type brings the type's control-defaults object
|
|
2514
2637
|
# ahead of the sections, which moves the sections along.
|
|
2515
|
-
objects = with_prototype(objects, kind, TYPE_CODES[control_type])
|
|
2638
|
+
objects = with_prototype(objects, kind, TYPE_CODES[control_type], theme)
|
|
2516
2639
|
at = next(i for i, o in enumerate(objects) if o.is_section and o.name == section)
|
|
2517
2640
|
end = at + 1
|
|
2518
2641
|
while end < len(objects) and not objects[end].is_section:
|
|
@@ -70,11 +70,9 @@ ACTION_IDS: dict[str, int] = {
|
|
|
70
70
|
}
|
|
71
71
|
ACTION_NAMES: dict[int, str] = {value: key for key, value in ACTION_IDS.items()}
|
|
72
72
|
|
|
73
|
-
#: `MSysObjects.Type` and `MSysNavPaneObjectIDs.Type` for a macro.
|
|
74
|
-
#: macro's object id steps by one where a module's steps by four.
|
|
73
|
+
#: `MSysObjects.Type` and `MSysNavPaneObjectIDs.Type` for a macro.
|
|
75
74
|
OBJECT_MACRO = -32766
|
|
76
75
|
NAV_MACRO_TYPE = 32770
|
|
77
|
-
OBJECT_ID_STEP = 1
|
|
78
76
|
|
|
79
77
|
|
|
80
78
|
@dataclass(frozen=True)
|