diff --git a/import_vba.py b/import_vba.py index 8a9354b..8c1d2cc 100644 --- a/import_vba.py +++ b/import_vba.py @@ -27,8 +27,8 @@ except ImportError: # ==================== 配置区域 ==================== # 从 .env 文件读取配置 -# 目标 xlsm 文件路径(用于导入VBA代码) -TARGET_XLSM_FILE = os.getenv("TARGET_XLSM_FILE", "").strip() or None +# 目标文件路径(支持 .xlsm / .accdb / .mdb) +TARGET_FILE = os.getenv("TARGET_FILE", "").strip() or None # VBA代码输出目录(如果未设置,则使用源文件同目录下的VBA文件夹) VBA_OUTPUT_DIR = os.getenv("VBA_OUTPUT_DIR", "").strip() or None # ================================================= @@ -39,6 +39,24 @@ CLASS_MODULE_DIR = "ClassModules" DOCUMENT_MODULE_DIR = "DocumentModules" FORMS_DIR = "Forms" +# 支持的文件类型 +ACCESS_EXTENSIONS = {'.accdb', '.mdb'} +EXCEL_EXTENSIONS = {'.xlsm', '.xls', '.xlsb'} + + +def get_file_type(file_path: Path) -> str: + """ + 根据文件扩展名判断文件类型 + + Returns: + 'access' 或 'excel' + """ + ext = file_path.suffix.lower() + if ext in ACCESS_EXTENSIONS: + return 'access' + return 'excel' + + class VBAImporter: """VBA代码导入器""" @@ -289,22 +307,152 @@ class VBAImporter: try: excel.Quit() except: pass + def import_vba_access(self): + """使用Access COM导入VBA代码""" + if not self.target_file.exists(): + print(f"错误: 找不到目标 Access 文件: {self.target_file}") + return False + + if not self.vba_dir.exists(): + print(f"错误: 找不到 VBA 代码目录: {self.vba_dir}") + return False + + print(f"正在打开 Access 数据库: {self.target_file.name} ...") + + access = None + try: + access = win32.Dispatch("Access.Application") + access.Visible = False + access.OpenCurrentDatabase(str(self.target_file)) + + try: + vb_project = access.VBE.VBProjects(1) + except Exception: + print("错误: 无法访问 VBA 项目。请确保信任对 VBA 工程对象模型的访问。") + return False + + print("开始导入模块...\n") + + # 扫描所有模块(Access 只导入标准模块和类模块) + scan_dirs = [ + (STANDARD_MODULE_DIR, ".bas"), + (CLASS_MODULE_DIR, ".cls"), + ] + modules = [] + for dir_name, ext in scan_dirs: + dir_path = self.vba_dir / dir_name + if not dir_path.exists(): + continue + for file_path in dir_path.glob(f"*{ext}"): + modules.append({ + "name": file_path.stem, + "type": dir_name, + "file_path": file_path, + "ext": ext + }) + + if not modules: + print("警告: 未找到任何 VBA 模块文件") + return False + + temp_files_created = [] + + for module_info in modules: + module_name = module_info["name"] + module_type_dir = module_info["type"] + source_code_path = module_info["file_path"] + + component = None + try: + component = vb_project.VBComponents(module_name) + except: + component = None + + # 移除已存在的组件 + if component: + try: + vb_project.VBComponents.Remove(component) + except Exception as e: + print(f" [警告] 无法移除 {module_name}: {e},将尝试更新代码") + # 回退到字符串注入 + try: + code_module = component.CodeModule + num_lines = code_module.CountOfLines + if num_lines > 0: + code_module.DeleteLines(1, num_lines) + with open(source_code_path, 'r', encoding='utf-8') as f: + new_code = f.read() + if new_code.strip(): + code_module.AddFromString(new_code) + print(f" [更新] {module_name} ({module_type_dir}) - 代码已更新") + except Exception as e2: + print(f" [错误] 更新代码 {module_name} 失败: {e2}") + continue + + # 生成临时导入文件并导入 + temp_file = self._reconstruct_file_content(source_code_path, module_name, module_type_dir) + temp_files_created.append(temp_file) + + try: + vb_project.VBComponents.Import(str(temp_file)) + print(f" [导入] {module_name} ({module_type_dir})") + except Exception as e: + print(f" [错误] 导入 {module_name} 失败: {e}") + + # 清理临时文件 + for p in temp_files_created: + try: + if p.exists(): p.unlink() + except: pass + try: + temp_dir = Path(tempfile.gettempdir()) / "vba_import_temp" + if temp_dir.exists(): shutil.rmtree(temp_dir) + except: pass + + print("\n正在保存...") + try: + import time + time.sleep(1) # 等待 VBE 完成 + print("已保存更改。") + except Exception as e: + print(f"保存时出错: {e}") + + print(f"\n导入完成!目标文件: {self.target_file.name}") + return True + + except Exception as e: + print(f"\n发生未处理的错误: {e}") + import traceback + traceback.print_exc() + return False + + finally: + if access: + try: + access.CloseCurrentDatabase() + except: + pass + try: + access.Quit() + except: + pass + def main(): print("=" * 60) - print("VBA代码导入工具 (V4.0 - 基于配置)") + print("VBA代码导入工具 (V5.0 - 支持Excel和Access)") print("=" * 60) print() script_dir = Path(__file__).parent # 检查配置 - if not TARGET_XLSM_FILE: - print("错误: 未配置 TARGET_XLSM_FILE") - print("请在 .env 文件中设置目标 Excel 文件路径") + if not TARGET_FILE: + print("错误: 未配置 TARGET_FILE") + print("请在 .env 文件中设置目标文件路径(支持.xlsm和.accdb)") return # 确定目标文件路径 - target_path = Path(TARGET_XLSM_FILE) + target_path = Path(TARGET_FILE) if not target_path.is_absolute(): target_path = script_dir / target_path @@ -312,8 +460,7 @@ def main(): print(f"错误: 配置的文件不存在: {target_path}") return - if not target_path.suffix.lower() == '.xlsm': - print(f"警告: 文件扩展名不是 .xlsm: {target_path.name}") + file_type = get_file_type(target_path) # 确定 VBA 代码目录 if VBA_OUTPUT_DIR: @@ -332,13 +479,14 @@ def main(): print(" 2. 或在 .env 文件中设置 VBA_OUTPUT_DIR 指定代码目录") return - print(f"目标 Excel 文件: {target_path.name}") + type_label = "Access" if file_type == "access" else "Excel" + print(f"目标文件: {target_path.name} ({type_label})") print(f"VBA 代码目录: {vba_path}") print() # 确认操作 print("=" * 60) - print("警告: 此操作将覆盖目标 Excel 文件中的 VBA 代码。") + print(f"警告: 此操作将覆盖目标{type_label}文件中的 VBA 代码。") choice = input("\n确认继续? (y/n): ").lower().strip() if choice != 'y': @@ -354,7 +502,11 @@ def main(): print("=" * 60) print() - success = importer.import_vba() + # 根据文件类型选择导入方法 + if file_type == 'access': + success = importer.import_vba_access() + else: + success = importer.import_vba() print() print("=" * 60)