Files
VBAExtractor/docs/plans/2026-05-11-access-support-plan.md
2026-05-11 09:57:26 +08:00

630 lines
20 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Access (.accdb) VBA Support Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Add Access `.accdb` VBA extraction and import support alongside existing Excel `.xlsm` support, using a unified `TARGET_FILE` config variable.
**Architecture:** Extend the existing `VBAExtractor` and `VBAImporter` classes with Access COM methods. File type auto-detected by extension (`.xlsm` → Excel, `.accdb` → Access). Reuse existing module processing pipeline (`parse_attributes`, `_process_module`, `_scan_modules`, `_reconstruct_file_content`).
**Tech Stack:** pywin32 (COM automation), Access.Application ProgID
---
### Task 1: Update `.env` config variable
**Files:**
- Modify: `.env:8`
- Modify: `.env:5-8` (comments)
**Step 1: Rename variable and update comments**
Change `.env` from:
```
TARGET_XLSM_FILE=C:\Users\Administrator\Desktop\生产周期核对\常规产品生产周期.xlsm
```
To:
```
TARGET_FILE=C:\Users\Administrator\Desktop\生产周期核对\常规产品生产周期.xlsm
```
Update the comment block (lines 5-7) to reflect the new unified variable that supports both `.xlsm` and `.accdb`.
**Step 2: Commit**
```bash
git add .env
git commit -m "refactor: rename TARGET_XLSM_FILE to TARGET_FILE for unified file type support"
```
---
### Task 2: Update `extract_vba.py` — config, constructor, and file type helper
**Files:**
- Modify: `extract_vba.py:23` (config var)
- Modify: `extract_vba.py:38-69` (constructor — rename `self.xlsm_path` to `self.source_path`)
- Modify: `extract_vba.py:129,171` (references to `self.xlsm_path`)
- Add: file type helper function after constants block
**Step 1: Replace `TARGET_XLSM_FILE` with `TARGET_FILE`**
Line 23: `TARGET_XLSM_FILE``TARGET_FILE`
Lines 21-26 — update comment block accordingly.
**Step 2: Add file type helper function**
Add after the constants block (after line 32), before the class:
```python
# 支持的文件类型
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'
```
**Step 3: Rename `self.xlsm_path` to `self.source_path` throughout the class**
In `__init__`: `self.xlsm_path``self.source_path` (line 46, 58)
In `extract_vba_modules_olevba`: `self.xlsm_path.name``self.source_path.name` and `str(self.xlsm_path)``str(self.source_path)` (lines 129, 132)
In `extract_vba_modules_com`: `self.xlsm_path.name``self.source_path.name` and `str(self.xlsm_path.absolute())``str(self.source_path.absolute())` (lines 171, 178)
Also update the docstring in `__init__` (line 43-44): `xlsm_path: xlsm文件路径``source_path: 源文件路径(支持.xlsm和.accdb`
Rename the parameter from `xlsm_path` to `source_path`.
**Step 4: Commit**
```bash
git add extract_vba.py
git commit -m "refactor: rename xlsm_path to source_path and add file type detection helper"
```
---
### Task 3: Add `extract_vba_modules_access_com()` to `VBAExtractor`
**Files:**
- Modify: `extract_vba.py` — add method after `extract_vba_modules_com()` (after line 245)
**Step 1: Add the Access COM extraction method**
Insert after line 245 (end of `extract_vba_modules_com`):
```python
def extract_vba_modules_access_com(self):
"""
使用COM接口从Access数据库提取VBA代码
需要: Microsoft Access + pywin32
"""
try:
import win32com.client as win32
except ImportError:
print("错误: 未安装pywin32库")
print("请运行: pip install pywin32")
return False
print(f"正在使用COM接口解析: {self.source_path.name}")
access = None
try:
access = win32.Dispatch("Access.Application")
access.Visible = False
access.OpenCurrentDatabase(str(self.source_path.absolute()))
# Access 通过 VBE 获取 VBProject
try:
vb_project = access.VBE.VBProjects(1)
except Exception:
print("错误: 无法访问VBA项目")
print("请确保: 1) Access信任中心设置'信任对VBA工程对象模型的访问'")
print(" 2) 数据库中包含VBA代码")
return False
print("开始提取VBA组件...\n")
# 遍历所有VBA组件
for component in vb_project.VBComponents:
module_name = component.Name
module_type = component.Type
# 获取代码
code_module = component.CodeModule
line_count = code_module.CountOfLines
if line_count > 0:
vba_code = code_module.Lines(1, line_count)
else:
vba_code = ""
# Access 只有标准模块(1)和类模块(2),按类型分类
# 1=标准模块, 2=类模块
type_name = {
1: STANDARD_MODULE_DIR,
2: CLASS_MODULE_DIR,
}.get(module_type, STANDARD_MODULE_DIR)
self._process_module(module_name, vba_code, type_name)
print(f"\n提取完成!")
print(f"- 标准模块: {self.modules_dir}")
print(f"- 类模块: {self.class_modules_dir}")
return True
except Exception as e:
print(f"使用COM提取Access VBA代码时出错: {e}")
print("\n提示:")
print("1. 确保已安装Microsoft Access")
print("2. 打开Access -> 文件 -> 选项 -> 信任中心 -> 信任中心设置")
print("3. 勾选'信任对VBA工程对象模型的访问'")
return False
finally:
if access:
try:
access.CloseCurrentDatabase()
except:
pass
try:
access.Quit()
except:
pass
```
**Step 2: Commit**
```bash
git add extract_vba.py
git commit -m "feat: add Access COM extraction method to VBAExtractor"
```
---
### Task 4: Update `extract_vba.py` `main()` for unified file dispatch
**Files:**
- Modify: `extract_vba.py:342-438` (entire `main()` function)
**Step 1: Rewrite `main()` to support both file types**
Replace the entire `main()` function. Key changes:
- `TARGET_XLSM_FILE``TARGET_FILE`
- File validation accepts both `.xlsm` and `.accdb`
- Interactive mode scans for both extensions
- Auto-selects COM method for `.accdb` (skips olevba prompt)
- Instantiates `VBAExtractor` with `source_path` parameter
```python
def main():
"""主函数"""
print("=" * 60)
print("VBA代码提取工具")
print("=" * 60)
print()
# 检查是否配置了目标文件
if TARGET_FILE and TARGET_FILE.strip():
# 使用配置的文件路径
script_dir = Path(__file__).parent
target_path = Path(TARGET_FILE)
# 如果是相对路径,则相对于脚本所在目录
if not target_path.is_absolute():
target_path = script_dir / target_path
if not target_path.exists():
print(f"错误: 配置的文件不存在: {target_path}")
return
file_type = get_file_type(target_path)
source_file = target_path
print(f"使用配置文件: {source_file.name} ({file_type})")
print()
else:
# 交互模式:查找支持的文件
excel_dir = Path("Excel")
if not excel_dir.exists():
print("错误: 未找到Excel文件夹")
return
# 同时扫描 Excel 和 Access 文件
all_files = list(excel_dir.glob("*.xlsm")) + list(excel_dir.glob("*.accdb")) + list(excel_dir.glob("*.mdb"))
if not all_files:
print("错误: Excel文件夹中没有.xlsm或.accdb文件")
return
# 如果有多个文件,让用户选择
if len(all_files) > 1:
print("发现多个文件:")
for i, f in enumerate(all_files, 1):
ft = get_file_type(f)
print(f" {i}. {f.name} ({ft})")
print()
choice = input("请选择文件编号 (直接回车选择第1个): ").strip()
if not choice:
source_file = all_files[0]
else:
try:
idx = int(choice) - 1
source_file = all_files[idx]
except:
print("无效选择,使用第一个文件")
source_file = all_files[0]
else:
source_file = all_files[0]
file_type = get_file_type(source_file)
print()
print(f"选择文件: {source_file.name} ({file_type})")
print()
# 创建提取器
extractor = VBAExtractor(str(source_file))
# 显示输出目录信息
print(f"输出目录: {extractor.output_dir}")
print()
# 根据文件类型选择提取方法
if file_type == 'access':
# Access 只支持COM方法
print("Access文件仅支持COM接口提取...")
success = extractor.extract_vba_modules_access_com()
else:
# Excel 支持COM和olevba
print("请选择提取方法:")
print(" 1. COM接口 (推荐 - 需要安装Excel)")
print(" 2. olevba库 (不需要Excel)")
print()
method = input("请选择 (直接回车使用方法1): ").strip()
if method == "2":
print("\n使用olevba库提取...")
success = extractor.extract_vba_modules_olevba()
else:
print("\n使用COM接口提取...")
success = extractor.extract_vba_modules_com()
if success:
print("\n" + "=" * 60)
print("提取成功完成!")
print("=" * 60)
else:
print("\n" + "=" * 60)
print("提取失败")
print("=" * 60)
```
**Step 2: Commit**
```bash
git add extract_vba.py
git commit -m "feat: update extract_vba.py main() to support both Excel and Access files"
```
---
### Task 5: Update `import_vba.py` — config and add Access import method
**Files:**
- Modify: `import_vba.py:31` (config var)
- Modify: `import_vba.py:42-290` (class — add `import_vba_access()` method)
**Step 1: Replace `TARGET_XLSM_FILE` with `TARGET_FILE`**
Line 31: `TARGET_XLSM_FILE``TARGET_FILE`
Lines 28-34 — update comment block accordingly.
**Step 2: Add `import_vba_access()` method to `VBAImporter`**
Insert after `import_vba()` method (after line 290, before the `finally` block closes and `main()` starts). Actually, insert as a new method on the class after `import_vba()`:
```python
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:
# Access 使用 RunCommand 保存
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
```
**Step 3: Commit**
```bash
git add import_vba.py
git commit -m "feat: add Access COM import method to VBAImporter"
```
---
### Task 6: Update `import_vba.py` `main()` for unified file dispatch
**Files:**
- Modify: `import_vba.py:292-368` (entire `main()` function)
**Step 1: Rewrite `main()` to support both file types**
Replace the entire `main()` function. Key changes:
- `TARGET_XLSM_FILE``TARGET_FILE`
- File validation accepts both `.xlsm` and `.accdb`
- Auto-selects import method based on file type
- Display appropriate warning message
```python
def main():
print("=" * 60)
print("VBA代码导入工具 (V5.0 - 支持Excel和Access)")
print("=" * 60)
print()
script_dir = Path(__file__).parent
# 检查配置
if not TARGET_FILE:
print("错误: 未配置 TARGET_FILE")
print("请在 .env 文件中设置目标文件路径(支持.xlsm和.accdb")
return
# 确定目标文件路径
target_path = Path(TARGET_FILE)
if not target_path.is_absolute():
target_path = script_dir / target_path
if not target_path.exists():
print(f"错误: 配置的文件不存在: {target_path}")
return
file_type = get_file_type(target_path)
# 确定 VBA 代码目录
if VBA_OUTPUT_DIR:
vba_path = Path(VBA_OUTPUT_DIR)
if not vba_path.is_absolute():
vba_path = script_dir / vba_path
else:
# 使用目标文件同目录下的 VBA 文件夹
vba_path = target_path.parent / "VBA"
if not vba_path.exists():
print(f"错误: VBA 代码目录不存在: {vba_path}")
print()
print("提示:")
print(" 1. 确保已运行 extract_vba.py 提取 VBA 代码")
print(" 2. 或在 .env 文件中设置 VBA_OUTPUT_DIR 指定代码目录")
return
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(f"警告: 此操作将覆盖目标{type_label}文件中的 VBA 代码。")
choice = input("\n确认继续? (y/n): ").lower().strip()
if choice != 'y':
print("操作已取消")
return
importer = VBAImporter(str(vba_path), str(target_path))
# 显示模块数量
modules = importer._scan_modules()
print(f"找到 {len(modules)} 个模块文件")
print()
print("=" * 60)
print()
# 根据文件类型选择导入方法
if file_type == 'access':
success = importer.import_vba_access()
else:
success = importer.import_vba()
print()
print("=" * 60)
if success:
print("导入成功完成!")
else:
print("导入失败")
print("=" * 60)
```
Note: `main()` in `import_vba.py` also needs the `get_file_type` helper. Add the same helper function and constants to `import_vba.py` (or extract to a shared module — but per the design, we keep it simple with duplication since the helper is tiny).
**Step 2: Add the file type helper to `import_vba.py`**
Add after the constants block (after line 40), same as in `extract_vba.py`:
```python
# 支持的文件类型
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'
```
**Step 3: Commit**
```bash
git add import_vba.py
git commit -m "feat: update import_vba.py main() to support both Excel and Access files"
```
---
### Task 7: Update `.gitignore` and `CLAUDE.md` docs
**Files:**
- Modify: `.gitignore:17` (add `Access/` directory)
- Modify: `CLAUDE.md` (update docs to reflect Access support)
**Step 1: Add Access directory to `.gitignore`**
After line 17 (`Excel/`), add:
```
Access/
```
**Step 2: Update `CLAUDE.md`**
Update the project overview to mention Access support:
- Project description: mention `.accdb` alongside `.xlsm`
- Directory structure: add `Access/` source directory
- Common commands: update descriptions to mention Access files
- Platform requirements: add "Microsoft Access" as optional
**Step 3: Commit**
```bash
git add .gitignore CLAUDE.md
git commit -m "docs: update documentation for Access file support"
```