pyOpenVBA 6.3.3__tar.gz → 6.3.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/PKG-INFO +15 -5
  2. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/README.md +14 -4
  3. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/pyproject.toml +1 -1
  4. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/__init__.py +1 -1
  5. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/__main__.py +6 -0
  6. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/_host.py +10 -0
  7. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/access/_pages.py +2 -0
  8. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/access/_rows.py +19 -3
  9. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/access/_tdef.py +8 -0
  10. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/access/database.py +17 -1
  11. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/cfb.py +21 -1
  12. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/excel.py +5 -2
  13. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/mlang/_parse.py +8 -2
  14. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/powerpoint.py +3 -1
  15. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/powerquery/_metadata.py +1 -1
  16. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/powerquery/_package.py +13 -2
  17. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/vba.py +21 -1
  18. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/word.py +3 -1
  19. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/tests/fuzz_corpus/README.md +18 -0
  20. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/.gitignore +0 -0
  21. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/LICENSE.md +0 -0
  22. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/docs/architecture.md +0 -0
  23. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/docs/excel_checklist.csv +0 -0
  24. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/docs/host_completeness.md +0 -0
  25. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/docs/ms-ovba-implementation-guide_v2.md +0 -0
  26. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/docs/power_query.md +0 -0
  27. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/docs/research/access_complex/README.md +0 -0
  28. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/docs/research/access_designs/README.md +0 -0
  29. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/docs/research/access_macros/README.md +0 -0
  30. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/docs/research/access_write/README.md +0 -0
  31. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/docs/research/pcode/README.md +0 -0
  32. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/docs/roadmap.md +0 -0
  33. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/docs/vba_runtime.md +0 -0
  34. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/_a1.py +0 -0
  35. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/_deflate.py +0 -0
  36. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/_new_project.py +0 -0
  37. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/_oforms_pages.py +0 -0
  38. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/_oforms_records.py +0 -0
  39. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/_package_signature.py +0 -0
  40. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/_ppt_container.py +0 -0
  41. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/_references.py +0 -0
  42. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/_relationships.py +0 -0
  43. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/_templates/__init__.py +0 -0
  44. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/_templates/blank_files/blank_database.accdb +0 -0
  45. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/_templates/blank_files/blank_database_module.accdb +0 -0
  46. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/_templates/blank_files/blank_document.docm +0 -0
  47. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/_templates/blank_files/blank_excel_addin.xlam +0 -0
  48. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/_templates/blank_files/blank_presentation.pptm +0 -0
  49. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/_templates/blank_files/blank_workbook.xlsb +0 -0
  50. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/_templates/blank_files/blank_workbook.xlsm +0 -0
  51. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/_templates/blank_files/blank_workbook.xlsx +0 -0
  52. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/_templates/blank_files/engine_skeleton.accdb +0 -0
  53. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/_templates/blank_files/engine_skeleton.mdb +0 -0
  54. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/_templates/designs/form.blob +0 -0
  55. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/_templates/designs/form.lvprop +0 -0
  56. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/_templates/designs/form.prototypes +0 -0
  57. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/_templates/designs/form.typeinfo +0 -0
  58. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/_templates/designs/report.blob +0 -0
  59. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/_templates/designs/report.lvprop +0 -0
  60. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/_templates/designs/report.prototypes +0 -0
  61. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/_templates/designs/report.typeinfo +0 -0
  62. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/_xml.py +0 -0
  63. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/access/__init__.py +0 -0
  64. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/access/_alloc.py +0 -0
  65. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/access/_btree.py +0 -0
  66. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/access/_collation.py +0 -0
  67. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/access/_collation_general_legacy.py +0 -0
  68. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/access/_compact.py +0 -0
  69. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/access/_complex.py +0 -0
  70. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/access/_datapage.py +0 -0
  71. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/access/_ddl.py +0 -0
  72. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/access/_designs.py +0 -0
  73. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/access/_facade.py +0 -0
  74. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/access/_format.py +0 -0
  75. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/access/_index.py +0 -0
  76. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/access/_lval.py +0 -0
  77. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/access/_macros.py +0 -0
  78. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/access/_props.py +0 -0
  79. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/access/_queries.py +0 -0
  80. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/access/_schema.py +0 -0
  81. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/access/_sql.py +0 -0
  82. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/access/_storage.py +0 -0
  83. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/access/_themes.py +0 -0
  84. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/access/_validate.py +0 -0
  85. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/access/_vba.py +0 -0
  86. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/access_read.py +0 -0
  87. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/__init__.py +0 -0
  88. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/__init__.py +0 -0
  89. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_arrays.py +0 -0
  90. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_autofill.py +0 -0
  91. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_autofilter.py +0 -0
  92. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_bridge.py +0 -0
  93. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_calc.py +0 -0
  94. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_cell_styles.py +0 -0
  95. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_clipboard.py +0 -0
  96. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_control_refs.py +0 -0
  97. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_controls.py +0 -0
  98. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_copy_names.py +0 -0
  99. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_dimensions.py +0 -0
  100. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_duplicates.py +0 -0
  101. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_dynamic_file.py +0 -0
  102. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_editing.py +0 -0
  103. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_engine_book.py +0 -0
  104. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_evaluate.py +0 -0
  105. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_events.py +0 -0
  106. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_find.py +0 -0
  107. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_font_rows.py +0 -0
  108. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_formats.py +0 -0
  109. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_formula_format.py +0 -0
  110. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_hyperlinks.py +0 -0
  111. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_hyperlinks_file.py +0 -0
  112. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_io.py +0 -0
  113. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_merges.py +0 -0
  114. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_model.py +0 -0
  115. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_named_api.py +0 -0
  116. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_names.py +0 -0
  117. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_notes.py +0 -0
  118. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_notes_file.py +0 -0
  119. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_number_format.py +0 -0
  120. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_protection.py +0 -0
  121. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_radios.py +0 -0
  122. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_refresh.py +0 -0
  123. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_region.py +0 -0
  124. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_row_formats.py +0 -0
  125. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_shape_api.py +0 -0
  126. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_shapes.py +0 -0
  127. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_shared.py +0 -0
  128. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_sheet_copy.py +0 -0
  129. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_sheet_move.py +0 -0
  130. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_sort.py +0 -0
  131. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_special_cells.py +0 -0
  132. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_spills.py +0 -0
  133. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_styles.py +0 -0
  134. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_table_edits.py +0 -0
  135. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_tables.py +0 -0
  136. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_text_columns.py +0 -0
  137. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_typing.py +0 -0
  138. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_validation.py +0 -0
  139. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_visible.py +0 -0
  140. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_windows.py +0 -0
  141. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/excel/_worksheet_functions.py +0 -0
  142. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/powerpoint/__init__.py +0 -0
  143. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/powerpoint/_bridge.py +0 -0
  144. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/powerpoint/_io.py +0 -0
  145. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/powerpoint/_model.py +0 -0
  146. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/word/__init__.py +0 -0
  147. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/word/_bridge.py +0 -0
  148. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/word/_io.py +0 -0
  149. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/apps/word/_model.py +0 -0
  150. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/exceptions.py +0 -0
  151. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/forms.py +0 -0
  152. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/__init__.py +0 -0
  153. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_arity.py +0 -0
  154. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_calc/__init__.py +0 -0
  155. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_calc/catalog.py +0 -0
  156. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_calc/cells.py +0 -0
  157. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_calc/collate.py +0 -0
  158. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_calc/dates.py +0 -0
  159. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_calc/evaluator.py +0 -0
  160. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_calc/functions/__init__.py +0 -0
  161. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_calc/functions/aggregate.py +0 -0
  162. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_calc/functions/arithmetic.py +0 -0
  163. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_calc/functions/arrays.py +0 -0
  164. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_calc/functions/common.py +0 -0
  165. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_calc/functions/conditional.py +0 -0
  166. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_calc/functions/database.py +0 -0
  167. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_calc/functions/dates.py +0 -0
  168. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_calc/functions/distributions.py +0 -0
  169. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_calc/functions/engineering.py +0 -0
  170. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_calc/functions/financial.py +0 -0
  171. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_calc/functions/information.py +0 -0
  172. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_calc/functions/lambdas.py +0 -0
  173. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_calc/functions/logical.py +0 -0
  174. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_calc/functions/lookup.py +0 -0
  175. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_calc/functions/matrix.py +0 -0
  176. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_calc/functions/statistics.py +0 -0
  177. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_calc/functions/subtotal.py +0 -0
  178. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_calc/functions/text.py +0 -0
  179. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_calc/host.py +0 -0
  180. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_calc/lexer.py +0 -0
  181. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_calc/nodes.py +0 -0
  182. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_calc/numbers.py +0 -0
  183. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_calc/parser.py +0 -0
  184. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_calc/precise.py +0 -0
  185. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_calc/reference.py +0 -0
  186. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_calc/registry.py +0 -0
  187. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_calc/special.py +0 -0
  188. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_calc/values.py +0 -0
  189. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_deep.py +0 -0
  190. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_display.py +0 -0
  191. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_formula2.py +0 -0
  192. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_inventory.py +0 -0
  193. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_parse.py +0 -0
  194. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_prefixes.py +0 -0
  195. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_r1c1.py +0 -0
  196. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_spell.py +0 -0
  197. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_structured.py +0 -0
  198. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/formula/_values.py +0 -0
  199. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/interpreter/__init__.py +0 -0
  200. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/interpreter/_ast.py +0 -0
  201. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/interpreter/_constants_data.py +0 -0
  202. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/interpreter/_intrinsics.py +0 -0
  203. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/interpreter/_inventory.py +0 -0
  204. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/interpreter/_inventory_data.py +0 -0
  205. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/interpreter/_lex.py +0 -0
  206. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/interpreter/_objects.py +0 -0
  207. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/interpreter/_parse.py +0 -0
  208. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/interpreter/_preprocess.py +0 -0
  209. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/interpreter/_runtime.py +0 -0
  210. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/interpreter/_scripting.py +0 -0
  211. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/interpreter/_values.py +0 -0
  212. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/mlang/__init__.py +0 -0
  213. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/mlang/_eval.py +0 -0
  214. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/mlang/_inventory.py +0 -0
  215. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/mlang/_inventory_data.py +0 -0
  216. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/mlang/_library.py +0 -0
  217. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/mlang/_values.py +0 -0
  218. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/powerquery/__init__.py +0 -0
  219. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/powerquery/_binary.py +0 -0
  220. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/powerquery/_files.py +0 -0
  221. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/powerquery/_mashup.py +0 -0
  222. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/powerquery/_opc.py +0 -0
  223. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/powerquery/_refresh.py +0 -0
  224. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/powerquery/_section.py +0 -0
  225. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/powerquery/_sheets.py +0 -0
  226. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/powerquery/workbook.py +0 -0
  227. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/shapes/__init__.py +0 -0
  228. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/shapes/_docx.py +0 -0
  229. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/shapes/_docx_text.py +0 -0
  230. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/shapes/_drawingml.py +0 -0
  231. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/shapes/_pptx.py +0 -0
  232. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/shapes/_values.py +0 -0
  233. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/shapes/_xlsx.py +0 -0
  234. {pyopenvba-6.3.3 → pyopenvba-6.3.5}/src/pyopenvba/vba_pcode.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pyOpenVBA
3
- Version: 6.3.3
3
+ Version: 6.3.5
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
@@ -41,6 +41,9 @@ Description-Content-Type: text/markdown
41
41
  [![Python versions](https://img.shields.io/pypi/pyversions/pyOpenVBA.svg)](https://pypi.org/project/pyOpenVBA/)
42
42
  [![CI](https://github.com/WilliamSmithEdward/pyOpenVBA/actions/workflows/ci.yml/badge.svg)](https://github.com/WilliamSmithEdward/pyOpenVBA/actions/workflows/ci.yml)
43
43
  [![Security](https://github.com/WilliamSmithEdward/pyOpenVBA/actions/workflows/security.yml/badge.svg)](https://github.com/WilliamSmithEdward/pyOpenVBA/actions/workflows/security.yml)
44
+ [![Malware scan](https://github.com/WilliamSmithEdward/pyOpenVBA/actions/workflows/malware-scan.yml/badge.svg)](https://github.com/WilliamSmithEdward/pyOpenVBA/actions/workflows/malware-scan.yml)
45
+ [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/WilliamSmithEdward/pyOpenVBA/badge)](https://scorecard.dev/viewer/?uri=github.com/WilliamSmithEdward/pyOpenVBA)
46
+ [![OpenSSF Best Practices](https://www.bestpractices.dev/projects/15116/badge)](https://www.bestpractices.dev/projects/15116)
44
47
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/WilliamSmithEdward/pyOpenVBA/blob/main/LICENSE.md)
45
48
  [![Downloads](https://static.pepy.tech/badge/pyOpenVBA/month)](https://pepy.tech/project/pyOpenVBA)
46
49
 
@@ -52,9 +55,9 @@ Works on Windows, macOS and Linux. Python 3.10 or newer.
52
55
 
53
56
  VBA, four hosts and one API:
54
57
 
55
- * Excel (`.xlsm`, `.xlsb`, `.xlam`, `.xls`)
56
- * Word (`.docm`, `.dotm`, `.doc`)
57
- * PowerPoint (`.pptm`, `.potm`, `.ppt`)
58
+ * Excel (`.xlsm`, `.xlsb`, `.xlam`, `.xls`; `.xltm`, `.xlt`, `.xla` read only)
59
+ * Word (`.docm`, `.dotm`, `.doc`; `.dot` read only)
60
+ * PowerPoint (`.pptm`, `.potm`, `.ppt`; `.ppsm`, `.ppam` read only)
58
61
  * Access (`.accdb`, `.mdb`)
59
62
 
60
63
  Power Query, in any Excel package:
@@ -884,6 +887,9 @@ implemented, what is not, and how it was measured is in
884
887
  | `.xlsb` | Binary workbook | yes | yes | yes |
885
888
  | `.xlam` | Macro-enabled add-in | yes | yes | yes |
886
889
  | `.xls` | Legacy (Excel 97-2003) | yes | - | no |
890
+ | `.xltm` | Macro-enabled template | read | yes | no |
891
+ | `.xlt` | Legacy template | read | - | no |
892
+ | `.xla` | Legacy add-in | read | - | no |
887
893
 
888
894
  A `.xlsx` file has no VBA project by design, and Power Query lives
889
895
  outside the project, so it is read and written in every package above.
@@ -895,6 +901,7 @@ outside the project, so it is read and written in every package above.
895
901
  | `.docm` | Macro-enabled document | yes | yes | yes |
896
902
  | `.dotm` | Macro-enabled template | yes | yes | no |
897
903
  | `.doc` | Legacy (Word 97-2003) | yes | yes | no |
904
+ | `.dot` | Legacy template | yes | no | no |
898
905
 
899
906
  ### PowerPoint
900
907
 
@@ -902,6 +909,8 @@ outside the project, so it is read and written in every package above.
902
909
  |-----------|------------------------------|:----:|:-----:|:----------:|
903
910
  | `.pptm` | Macro-enabled presentation | yes | yes | yes |
904
911
  | `.potm` | Macro-enabled template | yes | yes | no |
912
+ | `.ppsm` | Macro-enabled show | yes | no | no |
913
+ | `.ppam` | Macro-enabled add-in | yes | no | no |
905
914
  | `.ppt` | Legacy (PowerPoint 97-2003) | yes | yes | no |
906
915
 
907
916
  ### Files with no VBA project
@@ -1116,7 +1125,8 @@ not in a public issue.
1116
1125
  [SECURITY.md](https://github.com/WilliamSmithEdward/pyOpenVBA/blob/main/SECURITY.md)
1117
1126
  says what to report, which versions get fixes and how the code is
1118
1127
  checked. CodeQL and Semgrep scan every push to main and every pull
1119
- request, and every release carries its security report.
1128
+ request, ClamAV and YARA-X scan every file daily as well, and every
1129
+ release carries both reports.
1120
1130
 
1121
1131
  ---
1122
1132
 
@@ -4,6 +4,9 @@
4
4
  [![Python versions](https://img.shields.io/pypi/pyversions/pyOpenVBA.svg)](https://pypi.org/project/pyOpenVBA/)
5
5
  [![CI](https://github.com/WilliamSmithEdward/pyOpenVBA/actions/workflows/ci.yml/badge.svg)](https://github.com/WilliamSmithEdward/pyOpenVBA/actions/workflows/ci.yml)
6
6
  [![Security](https://github.com/WilliamSmithEdward/pyOpenVBA/actions/workflows/security.yml/badge.svg)](https://github.com/WilliamSmithEdward/pyOpenVBA/actions/workflows/security.yml)
7
+ [![Malware scan](https://github.com/WilliamSmithEdward/pyOpenVBA/actions/workflows/malware-scan.yml/badge.svg)](https://github.com/WilliamSmithEdward/pyOpenVBA/actions/workflows/malware-scan.yml)
8
+ [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/WilliamSmithEdward/pyOpenVBA/badge)](https://scorecard.dev/viewer/?uri=github.com/WilliamSmithEdward/pyOpenVBA)
9
+ [![OpenSSF Best Practices](https://www.bestpractices.dev/projects/15116/badge)](https://www.bestpractices.dev/projects/15116)
7
10
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/WilliamSmithEdward/pyOpenVBA/blob/main/LICENSE.md)
8
11
  [![Downloads](https://static.pepy.tech/badge/pyOpenVBA/month)](https://pepy.tech/project/pyOpenVBA)
9
12
 
@@ -15,9 +18,9 @@ Works on Windows, macOS and Linux. Python 3.10 or newer.
15
18
 
16
19
  VBA, four hosts and one API:
17
20
 
18
- * Excel (`.xlsm`, `.xlsb`, `.xlam`, `.xls`)
19
- * Word (`.docm`, `.dotm`, `.doc`)
20
- * PowerPoint (`.pptm`, `.potm`, `.ppt`)
21
+ * Excel (`.xlsm`, `.xlsb`, `.xlam`, `.xls`; `.xltm`, `.xlt`, `.xla` read only)
22
+ * Word (`.docm`, `.dotm`, `.doc`; `.dot` read only)
23
+ * PowerPoint (`.pptm`, `.potm`, `.ppt`; `.ppsm`, `.ppam` read only)
21
24
  * Access (`.accdb`, `.mdb`)
22
25
 
23
26
  Power Query, in any Excel package:
@@ -847,6 +850,9 @@ implemented, what is not, and how it was measured is in
847
850
  | `.xlsb` | Binary workbook | yes | yes | yes |
848
851
  | `.xlam` | Macro-enabled add-in | yes | yes | yes |
849
852
  | `.xls` | Legacy (Excel 97-2003) | yes | - | no |
853
+ | `.xltm` | Macro-enabled template | read | yes | no |
854
+ | `.xlt` | Legacy template | read | - | no |
855
+ | `.xla` | Legacy add-in | read | - | no |
850
856
 
851
857
  A `.xlsx` file has no VBA project by design, and Power Query lives
852
858
  outside the project, so it is read and written in every package above.
@@ -858,6 +864,7 @@ outside the project, so it is read and written in every package above.
858
864
  | `.docm` | Macro-enabled document | yes | yes | yes |
859
865
  | `.dotm` | Macro-enabled template | yes | yes | no |
860
866
  | `.doc` | Legacy (Word 97-2003) | yes | yes | no |
867
+ | `.dot` | Legacy template | yes | no | no |
861
868
 
862
869
  ### PowerPoint
863
870
 
@@ -865,6 +872,8 @@ outside the project, so it is read and written in every package above.
865
872
  |-----------|------------------------------|:----:|:-----:|:----------:|
866
873
  | `.pptm` | Macro-enabled presentation | yes | yes | yes |
867
874
  | `.potm` | Macro-enabled template | yes | yes | no |
875
+ | `.ppsm` | Macro-enabled show | yes | no | no |
876
+ | `.ppam` | Macro-enabled add-in | yes | no | no |
868
877
  | `.ppt` | Legacy (PowerPoint 97-2003) | yes | yes | no |
869
878
 
870
879
  ### Files with no VBA project
@@ -1079,7 +1088,8 @@ not in a public issue.
1079
1088
  [SECURITY.md](https://github.com/WilliamSmithEdward/pyOpenVBA/blob/main/SECURITY.md)
1080
1089
  says what to report, which versions get fixes and how the code is
1081
1090
  checked. CodeQL and Semgrep scan every push to main and every pull
1082
- request, and every release carries its security report.
1091
+ request, ClamAV and YARA-X scan every file daily as well, and every
1092
+ release carries both reports.
1083
1093
 
1084
1094
  ---
1085
1095
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "pyOpenVBA"
7
- version = "6.3.3"
7
+ version = "6.3.5"
8
8
  description = "Read and write VBA macros and Excel Power Query in pure Python, no dependencies."
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -293,4 +293,4 @@ def push_power_query(
293
293
  """
294
294
  return _push_queries(src_dir, workbook, out=out, encoding=encoding, remove_missing=remove_missing)
295
295
 
296
- __version__ = "6.3.3"
296
+ __version__ = "6.3.5"
@@ -179,12 +179,18 @@ _HOST_BY_SUFFIX: dict[str, type] = {
179
179
  ".xlsm": ExcelFile,
180
180
  ".xlsb": ExcelFile,
181
181
  ".xlam": ExcelFile,
182
+ ".xltm": ExcelFile,
182
183
  ".xls": ExcelFile,
184
+ ".xlt": ExcelFile,
185
+ ".xla": ExcelFile,
183
186
  ".docm": WordFile,
184
187
  ".dotm": WordFile,
185
188
  ".doc": WordFile,
189
+ ".dot": WordFile,
186
190
  ".pptm": PowerPointFile,
187
191
  ".potm": PowerPointFile,
192
+ ".ppsm": PowerPointFile,
193
+ ".ppam": PowerPointFile,
188
194
  ".ppt": PowerPointFile,
189
195
  }
190
196
 
@@ -91,6 +91,8 @@ class VBAHostFile(ReferenceManager):
91
91
  _project_storage: ClassVar[str | None]
92
92
  #: The formats add_vba_project gives a project to, each measured against its application.
93
93
  _project_formats: ClassVar[frozenset[str]] = frozenset()
94
+ #: Formats that open and read but do not save: a writer for them has not been measured.
95
+ _read_only_formats: ClassVar[frozenset[str]] = frozenset()
94
96
  #: The package part a zip-based file's project is related from.
95
97
  _main_part: ClassVar[str] = ""
96
98
 
@@ -533,7 +535,15 @@ class VBAHostFile(ReferenceManager):
533
535
 
534
536
  A file with no VBA project can hold no edit, since every write
535
537
  refuses, so it is written out as it was read.
538
+
539
+ Templates and add-ins that share a supported layout (``.xltm``,
540
+ ``.xlt``, ``.xla``, ``.dot``, ``.ppsm``, ``.ppam``) open read only:
541
+ saving one raises :class:`~pyopenvba.exceptions.UnsupportedFormatError`.
536
542
  """
543
+ if self._suffix in self._read_only_formats:
544
+ raise UnsupportedFormatError(
545
+ f"Saving a {self._suffix} file is not supported; it opens read only."
546
+ )
537
547
  if not self._has_project:
538
548
  if dest is not None:
539
549
  Path(dest).write_bytes(self._container_raw)
@@ -409,6 +409,8 @@ def row_bytes(
409
409
  target with ``overflow_target=True``, where the 0x8000 bit marks the
410
410
  moved row rather than a deletion."""
411
411
  slots = row_slots(page)
412
+ if not 0 <= slot < len(slots):
413
+ raise AccessError(f"slot {slot} out of range (0..{len(slots) - 1})")
412
414
  entry = slots[slot]
413
415
  if entry & ROW_DELETED and not overflow_target:
414
416
  return None
@@ -184,9 +184,13 @@ def encode_text(text: str) -> bytes:
184
184
 
185
185
  def decode_datetime(raw: bytes) -> _dt.datetime:
186
186
  value = struct.unpack("<d", raw)[0]
187
- days = int(value)
188
- fraction = abs(value - days)
189
- return EPOCH + _dt.timedelta(days=days) + _dt.timedelta(days=fraction)
187
+ try:
188
+ days = int(value)
189
+ fraction = abs(value - days)
190
+ return EPOCH + _dt.timedelta(days=days) + _dt.timedelta(days=fraction)
191
+ except (OverflowError, ValueError):
192
+ # Out of the year 1 to 9999, or not a number: a damaged value.
193
+ raise AccessError(f"date value {value!r} is outside the range a date can hold") from None
190
194
 
191
195
 
192
196
  def encode_datetime(value: _dt.datetime) -> bytes:
@@ -455,11 +459,23 @@ def encode_row(
455
459
  return bytes(row)
456
460
 
457
461
 
462
+ #: The bytes a fixed-size type's value takes; any other length is a damaged row.
463
+ _SCALAR_SIZES = {
464
+ TYPE_BYTE: 1, TYPE_INT: 2, TYPE_LONG: 4, TYPE_MONEY: 8, TYPE_FLOAT: 4, TYPE_DOUBLE: 8,
465
+ TYPE_DATETIME: 8, TYPE_GUID: 16, TYPE_COMPLEX: 4, TYPE_BIGINT: 8,
466
+ }
467
+
468
+
458
469
  def decode_scalar(column: ColumnDef, raw: bytes) -> object:
459
470
  """Decode a non-long-value column. ``raw`` is the present value."""
460
471
  code = column.type_code
461
472
  if code == TYPE_BOOLEAN:
462
473
  return True
474
+ size = _SCALAR_SIZES.get(code, 0)
475
+ if size and len(raw) != size:
476
+ raise AccessError(
477
+ f"column {column.name!r} holds {len(raw)} bytes where its type takes {size}; the row is damaged"
478
+ )
463
479
  if code == TYPE_BYTE:
464
480
  return raw[0]
465
481
  if code == TYPE_INT:
@@ -333,10 +333,14 @@ def read_definition_bytes(store: PageStore, page: int) -> tuple[bytes, list[int]
333
333
 
334
334
  def _read_name(buf: bytes, pos: int) -> tuple[str, int]:
335
335
  """A name is prefixed by its length as a word, and stored as UTF-16."""
336
+ if pos + 2 > len(buf):
337
+ raise AccessError("name runs past the end of the definition")
336
338
  length = struct.unpack_from("<H", buf, pos)[0]
337
339
  raw = buf[pos + 2 : pos + 2 + length]
338
340
  if len(raw) != length:
339
341
  raise AccessError("name runs past the end of the definition")
342
+ if length % 2:
343
+ raise AccessError(f"name is {length} bytes, an odd length for UTF-16")
340
344
  return raw.decode("utf-16-le"), pos + 2 + length
341
345
 
342
346
 
@@ -463,6 +467,10 @@ def parse_table_definition(store: PageStore, page: int) -> TableDefinition:
463
467
  pos += 2
464
468
  if number == INDEX_COLUMN_UNUSED:
465
469
  break
470
+ if pos + 8 > len(buf):
471
+ raise AccessError(
472
+ f"table definition at page {page} ends inside its usage-map block"
473
+ )
466
474
  column_usage_maps[number] = (u32(pos), u32(pos + 4))
467
475
  pos += 8
468
476
 
@@ -430,6 +430,22 @@ class Index:
430
430
  return encode_key(key_values, self.columns)
431
431
 
432
432
 
433
+ class Row(dict[str, object]):
434
+ """A decoded row: every column the table defines, by name.
435
+
436
+ A column the definition does not hold is an AccessError, not a bare
437
+ KeyError: when an engine table lacks one of its own columns, the
438
+ definition is damaged, and the message should say so (issue #45).
439
+ """
440
+
441
+ def __init__(self, table: str = "") -> None:
442
+ super().__init__()
443
+ self.table = table
444
+
445
+ def __missing__(self, column: str) -> object:
446
+ raise AccessError(f"table {self.table!r} has no column {column!r}; its definition is damaged")
447
+
448
+
433
449
  class Table:
434
450
  """A table: its definition plus the rows on its owned pages."""
435
451
 
@@ -778,7 +794,7 @@ class Table:
778
794
  return rid if ref.kind == LongValueRef.KIND_INLINE else RowId(ref.page, ref.row)
779
795
 
780
796
  def decode(self, raw: RawRow) -> dict[str, object]:
781
- out: dict[str, object] = {}
797
+ out = Row(self.name)
782
798
  for column in self.columns:
783
799
  value = raw.values.get(column.number)
784
800
  if value is None:
@@ -254,6 +254,12 @@ class CFB:
254
254
 
255
255
  if major_ver not in (3, 4):
256
256
  raise CFBError(f"Unsupported CFB major version: {major_ver}")
257
+ # [MS-CFB] 2.2: 512-byte sectors in version 3, 4096 in version 4, and
258
+ # 64-byte mini sectors in both.
259
+ if sector_size_pow != (9 if major_ver == 3 else 12) or mini_sector_size_pow != 6:
260
+ raise CFBError(
261
+ f"Unsupported CFB sector sizes: shift {sector_size_pow}, mini shift {mini_sector_size_pow}."
262
+ )
257
263
 
258
264
  self.sector_size = 1 << sector_size_pow # 512 or 4096
259
265
  self.mini_sector_size = 1 << mini_sector_size_pow # 64
@@ -264,11 +270,22 @@ class CFB:
264
270
 
265
271
  # Follow DIFAT chain for large files (> 109 FAT sectors)
266
272
  if difat_start != _ENDOFCHAIN and num_difat_sectors > 0:
273
+ # The header's count is only a claim: a file cannot hold more
274
+ # sectors than its size allows, and a chain that returns to a
275
+ # sector it has visited would otherwise loop for that count.
276
+ if num_difat_sectors > max(0, len(self._data) // self.sector_size - 1):
277
+ raise CFBError(f"The header claims {num_difat_sectors} DIFAT sectors; the file is too short.")
267
278
  sector = difat_start
279
+ seen: set[int] = set()
268
280
  for _ in range(num_difat_sectors):
269
281
  if sector in (_ENDOFCHAIN, _FREESECT):
270
282
  break
283
+ if sector in seen:
284
+ raise CFBError(f"The DIFAT chain returns to sector {sector}.")
285
+ seen.add(sector)
271
286
  sector_data = self._sector(sector)
287
+ if len(sector_data) < self.sector_size:
288
+ raise CFBError(f"DIFAT sector {sector} lies past the end of the file.")
272
289
  entries_per_difat = (self.sector_size // 4) - 1
273
290
  extra = [int(x) for x in struct.unpack_from(f"<{entries_per_difat}I", sector_data)]
274
291
  difat.extend(extra)
@@ -310,7 +327,10 @@ class CFB:
310
327
  self._mini_stream = bytes(ms_raw)
311
328
 
312
329
  def _sector(self, index: int) -> bytes:
313
- offset = _HEADER_SIZE + index * self.sector_size
330
+ # The header takes the whole first sector: 512 bytes in version 3, and
331
+ # 512 bytes padded to 4096 in version 4 ([MS-CFB] 2.2), so sector n
332
+ # starts at (n + 1) * sector_size, not after the 512 header bytes.
333
+ offset = (index + 1) * self.sector_size
314
334
  return self._data[offset: offset + self.sector_size]
315
335
 
316
336
  def _chain(self, start: int) -> list[int]:
@@ -6,6 +6,8 @@ Supports:
6
6
  - .xlsb (Binary workbook — ZIP containing xl/vbaProject.bin)
7
7
  - .xlam (Macro-enabled add-in — same ZIP container as .xlsm)
8
8
  - .xls (Legacy BIFF8 — the entire file is a CFB)
9
+ - .xltm, .xlt, .xla (templates and the legacy add-in: the .xlsm and
10
+ .xls containers; read only)
9
11
 
10
12
  Usage
11
13
  -----
@@ -27,8 +29,8 @@ from pyopenvba._host import VBAHostFile
27
29
  from pyopenvba._new_project import WORKBOOK_BASE, document_header, excel_code_name_edits, excel_code_names
28
30
  from pyopenvba.vba import VBAProject, split_attribute_header
29
31
 
30
- _ZIP_FORMATS = frozenset({".xlsm", ".xlsb", ".xlam"})
31
- _CFB_FORMATS = frozenset({".xls"})
32
+ _ZIP_FORMATS = frozenset({".xlsm", ".xlsb", ".xlam", ".xltm"})
33
+ _CFB_FORMATS = frozenset({".xls", ".xlt", ".xla"})
32
34
  _VBA_ENTRY = "xl/vbaProject.bin"
33
35
 
34
36
 
@@ -49,6 +51,7 @@ class ExcelFile(VBAHostFile):
49
51
  _application = "Excel"
50
52
  _project_storage = "_VBA_PROJECT_CUR"
51
53
  _project_formats = frozenset({".xlsm"})
54
+ _read_only_formats = frozenset({".xltm", ".xlt", ".xla"})
52
55
  _main_part = "xl/workbook.xml"
53
56
 
54
57
  # ------------------------------------------------------------------
@@ -219,7 +219,7 @@ def text_value(raw: str) -> str:
219
219
  for piece in body[index + 2 : close].split(","):
220
220
  if piece in _ESCAPES:
221
221
  out.append(_ESCAPES[piece])
222
- elif re.fullmatch(r"[0-9A-Fa-f]{4,8}", piece):
222
+ elif re.fullmatch(r"[0-9A-Fa-f]{4,8}", piece) and int(piece, 16) <= 0x10FFFF:
223
223
  out.append(chr(int(piece, 16)))
224
224
  else:
225
225
  out.append(f"#({piece})")
@@ -624,4 +624,10 @@ def parse(source: str) -> Node:
624
624
  body = source.strip()
625
625
  if not body:
626
626
  raise MSyntaxError("an empty formula")
627
- return Parser(tokenize(body), body).parse()
627
+ try:
628
+ return Parser(tokenize(body), body).parse()
629
+ except RecursionError:
630
+ # The parser descends once per nesting level, so a formula nested
631
+ # past Python's recursion limit, as a hostile workbook can hold, is
632
+ # refused like any other malformed formula.
633
+ raise MSyntaxError("the formula nests too deeply to parse") from None
@@ -4,6 +4,7 @@ PowerPoint file handler.
4
4
  Supports:
5
5
  - .pptm (OOXML macro-enabled presentation -- ZIP containing ppt/vbaProject.bin)
6
6
  - .potm (OOXML macro-enabled template -- ZIP containing ppt/vbaProject.bin)
7
+ - .ppsm, .ppam (show and add-in -- the .pptm container; read only)
7
8
  - .ppt (Legacy PowerPoint -- a CFB whose 'PowerPoint Document' stream
8
9
  embeds the VBA project as a deflated CFB; see
9
10
  :mod:`pyopenvba._ppt_container`)
@@ -28,7 +29,7 @@ from pyopenvba._host import VBAHostFile
28
29
  from pyopenvba.cfb import CFB
29
30
  from pyopenvba.vba import VBAProject
30
31
 
31
- _ZIP_FORMATS = frozenset({".pptm", ".potm"})
32
+ _ZIP_FORMATS = frozenset({".pptm", ".potm", ".ppsm", ".ppam"})
32
33
  _CFB_FORMATS = frozenset({".ppt"})
33
34
  _VBA_ENTRY = "ppt/vbaProject.bin"
34
35
 
@@ -51,6 +52,7 @@ class PowerPointFile(VBAHostFile):
51
52
  # A binary presentation embeds its project in a record, found by _vba_cfb_bytes.
52
53
  _project_storage = None
53
54
  _project_formats = frozenset({".pptm"})
55
+ _read_only_formats = frozenset({".ppsm", ".ppam"})
54
56
  _main_part = "ppt/presentation.xml"
55
57
 
56
58
  # A new project, as PowerPoint makes one: empty, and not written until
@@ -468,7 +468,7 @@ def parse_items(xml: bytes) -> list[Item]:
468
468
  """The items a ``LocalPackageMetadataFile`` document lists."""
469
469
  try:
470
470
  root = ElementTree.fromstring(xml.decode("utf-8-sig"))
471
- except ElementTree.ParseError as exc:
471
+ except (ElementTree.ParseError, UnicodeDecodeError) as exc:
472
472
  raise PowerQueryError(f"the metadata XML does not parse: {exc}") from exc
473
473
  if root.tag.rsplit("}", 1)[-1] != _ROOT:
474
474
  raise PowerQueryError(f"the metadata document is <{root.tag}>, not <{_ROOT}>")
@@ -85,17 +85,28 @@ class Package:
85
85
  parts: list[Part] = []
86
86
  at = 0
87
87
  while raw[at : at + 4] == b"PK\x03\x04":
88
+ if len(raw) < at + 30:
89
+ raise PowerQueryError("the package ends inside a part's header")
88
90
  (_need, _flags, method, dos_time, dos_date, _crc, csize, usize, nlen, xlen) = struct.unpack_from(
89
91
  "<HHHHHIIIHH", raw, at + 4
90
92
  )
91
- name = raw[at + 30 : at + 30 + nlen].decode("utf-8")
92
93
  body_at = at + 30 + nlen + xlen
94
+ if len(raw) < body_at + csize:
95
+ raise PowerQueryError("the package ends inside a part")
96
+ try:
97
+ name = raw[at + 30 : at + 30 + nlen].decode("utf-8")
98
+ except UnicodeDecodeError as exc:
99
+ raise PowerQueryError(f"a package part's name is not UTF-8: {exc}") from exc
93
100
  body = raw[body_at : body_at + csize]
94
101
  if method == 0:
95
102
  data = body
96
103
  elif method == DEFLATED:
104
+ # Inflate no more than the size the header states, plus one
105
+ # byte to tell a longer part, so a small part cannot expand
106
+ # without limit.
107
+ inflater = zlib.decompressobj(-15)
97
108
  try:
98
- data = zlib.decompress(body, -15)
109
+ data = inflater.decompress(body, usize + 1)
99
110
  except zlib.error as exc:
100
111
  raise PowerQueryError(f"the package part {name!r} does not inflate: {exc}") from exc
101
112
  else:
@@ -892,6 +892,23 @@ class VBAModule:
892
892
  )
893
893
 
894
894
 
895
+ #: Names the VBE will not give a module (VBComponents.Add fails with
896
+ #: 0x800AC3D4), measured in 64-bit Excel 16.0 (issue #40), and refused by a
897
+ #: rename too, which would make the same module. A
898
+ #: module so named still compiles, but code can never name it: ``Print.Hi``
899
+ #: is a syntax error and ``Me.Hi`` an invalid use of Me. Line, Width, Name,
900
+ #: Err, Mid, Time, Error, Reset, Beep, Load and Unload are accepted.
901
+ VBE_REFUSED_MODULE_NAMES = frozenset({
902
+ "circle", "pset", "scale", "print", "seek", "input", "get", "put", "open",
903
+ "tab", "spc", "debug", "me", "array", "lbound", "date", "stop",
904
+ })
905
+
906
+
907
+ def _check_module_name(name: str) -> None:
908
+ if name.casefold() in VBE_REFUSED_MODULE_NAMES:
909
+ raise ValueError(f"The VBE refuses {name!r} as a module name.")
910
+
911
+
895
912
  @dataclass
896
913
  class VBAProject:
897
914
  """Represents a parsed VBA project."""
@@ -963,8 +980,10 @@ class VBAProject:
963
980
  ``Attribute VB_Base`` ensured), so ``.cls`` files exported from
964
981
  the VBE are accepted as-is. See :func:`normalize_class_source`.
965
982
 
966
- Raises ``ValueError`` if a module with that name already exists.
983
+ Raises ``ValueError`` if a module with that name already exists, or
984
+ if the VBE refuses the name (see :data:`VBE_REFUSED_MODULE_NAMES`).
967
985
  """
986
+ _check_module_name(name)
968
987
  needle = name.casefold()
969
988
  if any(m.name.casefold() == needle for m in self.modules):
970
989
  raise ValueError(f"Module already exists: {name!r}")
@@ -1030,6 +1049,7 @@ class VBAProject:
1030
1049
  module = self.get_module(old_name)
1031
1050
  if module.name == new_name:
1032
1051
  return module
1052
+ _check_module_name(new_name)
1033
1053
  needle = new_name.casefold()
1034
1054
  if needle != module.name.casefold() and any(
1035
1055
  m.name.casefold() == needle for m in self.modules
@@ -5,6 +5,7 @@ Supports:
5
5
  - .docm (OOXML macro-enabled document -- ZIP containing word/vbaProject.bin)
6
6
  - .dotm (OOXML macro-enabled template -- ZIP containing word/vbaProject.bin)
7
7
  - .doc (Legacy Word -- the entire file is a CFB)
8
+ - .dot (Legacy template -- the .doc container; read only)
8
9
 
9
10
  Usage
10
11
  -----
@@ -25,7 +26,7 @@ from pathlib import Path
25
26
  from pyopenvba._host import VBAHostFile
26
27
 
27
28
  _ZIP_FORMATS = frozenset({".docm", ".dotm"})
28
- _CFB_FORMATS = frozenset({".doc"})
29
+ _CFB_FORMATS = frozenset({".doc", ".dot"})
29
30
  _VBA_ENTRY = "word/vbaProject.bin"
30
31
 
31
32
 
@@ -46,6 +47,7 @@ class WordFile(VBAHostFile):
46
47
  _application = "Word"
47
48
  _project_storage = "Macros"
48
49
  _project_formats = frozenset({".docm"})
50
+ _read_only_formats = frozenset({".dot"})
49
51
  _main_part = "word/document.xml"
50
52
 
51
53
  # A new project, as Word makes one: ThisDocument, from the Normal
@@ -19,11 +19,29 @@ crash, is treated as a fuzz regression.
19
19
  | `dir/` | `pyopenvba.vba._parse_dir_stream()` | `VBAProjectError`, `UnicodeDecodeError`, `IndexError`, `struct.error` |
20
20
  | `project/` | `pyopenvba.vba.parse_project_stream()` | `VBAProjectError`, `UnicodeDecodeError` |
21
21
  | `projectwm/` | `pyopenvba.vba.parse_projectwm()` | `VBAProjectError`, `UnicodeDecodeError` |
22
+ | `mashup/` | `pyopenvba.powerquery._mashup.Mashup.parse()` | `PowerQueryError` |
23
+ | `mformula/` | `pyopenvba.mlang._parse.parse()` | `MSyntaxError` |
22
24
 
23
25
  Each input file is **raw bytes** (extension is informational only;
24
26
  `.bin` by convention). Filenames should describe the seed, e.g.
25
27
  `empty.bin`, `truncated_header.bin`, `regression_2026_05_xxxx.bin`.
26
28
 
29
+ ## Coverage-guided fuzzing
30
+
31
+ [`fuzz/fuzz_parsers.py`](../../fuzz/fuzz_parsers.py) runs each target under
32
+ Atheris, seeded from its directory here, with the same acceptable
33
+ exceptions. The Fuzz workflow runs every target daily and on each change to
34
+ the parsers. To run one yourself on Linux with Python 3.12 or later:
35
+
36
+ ```
37
+ python -m pip install --require-hashes -r .github/requirements/fuzz.txt
38
+ python -m pip install --no-deps --no-build-isolation -e .
39
+ python fuzz/fuzz_parsers.py decompress -max_total_time=60 tests/fuzz_corpus/decompress
40
+ ```
41
+
42
+ A finding is written to a `crash-*` file. Minimise it, add it here as a
43
+ regression seed, and fix the parser.
44
+
27
45
  ## Adding a new seed
28
46
 
29
47
  When a fuzz run discovers a new failure mode, drop the offending bytes
File without changes
File without changes
File without changes
File without changes
File without changes