v1.1.0: 增加交互提示、路径输入、窗口属性配置

- main.py: 增加show_banner()启动说明、各阶段[INFO]日志、结果摘要、任意键退出
- file_selector.py: 重写为路径输入→验证→空输入弹窗回退→不存在循环重试
- run.bat: 新建启动脚本(chcp 65001, mode con cols=80 lines=20, color 0B, title固定署名, pause)
- Code/docs/modification-assessment.md: 修改需求评估文档
This commit is contained in:
2026-05-25 17:29:19 +08:00
parent 5fbc215e59
commit 836ad20515
35 changed files with 4105 additions and 25 deletions

View File

@@ -0,0 +1,860 @@
# PinMAP → PinList 转换器 — 全局架构设计
> **版本**: v1.0
> **日期**: 2026-05-25
> **架构师**: 脚本架构师 (Script Architect)
> **状态**: 待审批
---
## 1. 项目概述
### 1.1 背景
将 Excel 格式的 **PinMAP** 文件(方形封装引脚布局图)自动转换为 **PinList** 格式(引脚序号列表),消除手动抄录的低效与错误风险。
### 1.2 核心规则
- PinMAP 为方形/长方形结构,引脚沿四条边分布,左上角为 1 脚,**逆时针**排序
- 左上角 = 1 脚,右上角 = 上边最后一个脚,右下角 = 下边最后一个脚,左下角 = 左边最后一个脚
- 四个角点被相邻两边共享(不重复计数)
### 1.3 约束
| 约束项 | 说明 |
|--------|------|
| 运行平台 | Windows |
| 技术栈 | Python 标准库,**零第三方依赖** |
| 输入格式 | `.xls`(必须支持)、`.xlsx`(优先支持) |
| 输出格式 | `.xlsx` 仅 |
| 交互方式 | 命令行 + 文件选择对话框 |
---
## 2. 技术选型
### 2.1 为什么不用 openpyxl / xlrd
项目明确要求 **无第三方依赖**,因此必须使用 Python 标准库自行实现 Excel 读写。
### 2.2 XLS 读取 — BIFF8 二进制解析
`.xls` 是 Microsoft **BIFF8** 二进制格式(复合文档 OLE2 容器)。
**实现策略**
```
1. 使用 struct 模块解析 OLE2 复合文档头
2. 解析 FAT文件分配表定位 MiniFAT 和目录流
3. 定位 Workbook 流,读取 BIFF8 记录序列
4. 关键记录类型:
- 0x0009 (BOF) → 块起始标记
- 0x00FD (LABELSST) → 共享字符串表中的文本单元格
- 0x0006 (FORMULA) → 公式/数值单元格
- 0x0203 (NUMBER) → 数值单元格
- 0x000C (RK) → RK 数值(压缩整数/浮点)
- 0x000D (RString) → 内联字符串
- 0x00FC (STRING) → 字符串结果
- 0x0034 (SST) → 全局共享字符串表
- 0x0042 (BOUNDSHEET) → 工作表信息
5. 提取每个单元格的 (行, 列, 值) 三元组
```
**复杂度评估**中等。BIFF8 是固定长度记录流struct 解析直接。需处理 Unicode 编码BIFF8 默认 UTF-16LE部分兼容 ASCII
### 2.3 XLSX 读取 — ZIP + XML
`.xlsx` 本质是 ZIP 压缩包,内部为 Office Open XML (OOXML)。
**实现策略**
```python
import zipfile
import xml.etree.ElementTree as ET
1. zipfile.ZipFile 打开 .xlsx
2. 读取 [Content_Types].xml 确认结构
3. 读取 xl/workbook.xml 获取工作表关系
4. 读取 xl/worksheets/sheet1.xml 获取单元格数据
5. 读取 xl/sharedStrings.xml 获取共享字符串表
6. 解析单元格坐标 "A2" 列A行2和值类型
```
**复杂度评估**低。zipfile 和 xml.etree 均为标准库XML 结构规范清晰。
### 2.4 XLSX 写入 — ZIP + XML 生成
**实现策略**
```python
import zipfile
import xml.etree.ElementTree as ET
from io import BytesIO
1. 构建 OOXML 目录结构
[Content_Types].xml
_rels/.rels
xl/workbook.xml
xl/worksheets/sheet1.xml
xl/sharedStrings.xml
xl/_rels/workbook.xml.rels
2. 使用 zipfile.ZipFile 写入ZIP_DEFLATED
3. 关键 XML 构建
- sharedStrings.xml: 所有唯一字符串的 SST
- sheet1.xml: 单元格坐标 + si (SST index) 引用
- workbook.xml: 工作表引用
- [Content_Types].xml: MIME 类型声明
```
**复杂度评估**低。XML 结构固定,模板化生成即可。
### 2.5 技术选型总结
| 操作 | 格式 | 标准库模块 | 难度 |
|------|------|-----------|------|
| 读取 | xls | `struct` + 手动 OLE2/BIFF8 解析 | 中 |
| 读取 | xlsx | `zipfile` + `xml.etree.ElementTree` | 低 |
| 写入 | xlsx | `zipfile` + `xml.etree.ElementTree` | 低 |
| 文件选择 | — | `tkinter.filedialog` | 低 |
---
## 3. 模块划分
```
pinmap-to-pinlist/
├── Code/
│ ├── src/
│ │ ├── main.py # 入口:流程编排
│ │ ├── file_selector.py # 模块1文件选择
│ │ ├── xls_reader.py # 模块2aXLS 解析引擎
│ │ ├── xlsx_reader.py # 模块2bXLSX 解析引擎
│ │ ├── pinmap_parser.py # 模块3PinMAP 结构解析
│ │ ├── validator.py # 模块4数据验证
│ │ ├── pinlist_generator.py # 模块5PinList 生成
│ │ └── xlsx_writer.py # 模块6XLSX 输出引擎
│ └── docs/
│ └── architecture-design.md
├── Test/
└── Releases/
```
### 3.1 模块职责
#### 模块1`file_selector` — 文件选择
```python
def select_file() -> str | None:
"""弹出文件选择对话框,返回选中文件路径或 None取消"""
```
- 使用 `tkinter.filedialog.askopenfilename`
- 文件类型过滤:`*.xls;*.xlsx`
- 无 GUI 环境时回退到命令行参数
#### 模块2a`xls_reader` — XLS 解析引擎
```python
class XLSReader:
def __init__(self, filepath: str)
def read_all_cells(self) -> dict[tuple[int, int], str]:
"""返回 {(row, col): value} 字典,行列从 0 开始"""
def close(self)
```
**内部结构**
```
XLSReader
├── OLE2Parser → 解析复合文档,定位 Workbook 流
├── BIFF8Parser → 解析 BIFF8 记录流
│ ├── SSTParser → 共享字符串表
│ └── CellParser → 单元格记录
└── CellMap → 组装为 (row, col) → value 映射
```
#### 模块2b`xlsx_reader` — XLSX 解析引擎
```python
class XLSXReader:
def __init__(self, filepath: str)
def read_all_cells(self) -> dict[tuple[int, int], str]:
"""返回 {(row, col): value} 字典,行列从 0 开始"""
def close(self)
```
**内部结构**
```
XLSXReader
├── ZipExtractor → 解压 .xlsx 到内存
├── SharedStrings → 解析 sharedStrings.xml
├── SheetParser → 解析 sheet1.xml
│ ├── CoordParser → 列字母转索引 (A→0, B→1, ...)
│ └── CellParser → 提取单元格值
└── CellMap → 组装为 (row, col) → value 映射
```
#### 模块3`pinmap_parser` — PinMAP 结构解析
```python
def parse_pinmap(cells: dict[tuple[int, int], str]) -> PinMAP:
"""
解析步骤:
1. 排除 (0,0) 后扫描非空单元格,确定方形边界
2. 提取 A1 封装信息
3. 沿四条边提取引脚序号(边界单元格)和 PinName相邻内侧单元格
4. 逆时针遍历(左→下→右→上),按单元格位置去重(角点共享)
5. 返回 PinMAP 对象
"""
```
**解析算法**
```
Step 1: 确定方形边界
- 排除 (0,0)(封装信息单元格)
- 扫描所有非空单元格,找到最小/最大行号和列号
- width = max_col - min_col + 1
- height = max_row - min_row + 1
- 验证width >= 2 且 height >= 2
Step 2: 提取 A1 封装信息
- cells[(0, 0)] → package_info
Step 3: 构建 PinName 查找表
每条边的 PinName 位于序号单元格的"内侧相邻"位置:
左边:序号在 (r, min_col) Name 在 (r, min_col+1)
下边:序号在 (max_row, c) Name 在 (max_row-1, c)
右边:序号在 (r, max_col) Name 在 (r, max_col-1)
上边:序号在 (min_row, c) Name 在 (min_row+1, c)
Step 4: 逆时针遍历四条边(按单元格位置去重)
4a. 左边:从上到下 (row: min_row → max_row, col: min_col)
4b. 下边:从左到右 (row: max_row, col: min_col+1 → max_col)
4c. 右边:从下到上 (row: max_row-1 → min_row, col: max_col)
4d. 上边:从右到左 (row: min_row, col: max_col-1 → min_col)
角点去重:按 (row, col) 单元格位置去重,而非按 Pin 序号。
这样如果两个不同单元格恰好有相同序号validator 能检测到。
Step 5: 组装 Pin 列表
按逆时针顺序Pin1(左上角) → Pin2 → ... → PinN
```
#### 模块4`validator` — 数据验证
```python
def validate_pinmap(pinmap: PinMAP) -> ValidationResult:
"""
验证项:
1. Pin序号唯一性无重复
2. Pin序号连续性1..N 无间隔)
3. PinName 缺失检测warning默认 NC
4. 方形结构完整性width/height >= 2
"""
```
#### 模块5`pinlist_generator` — PinList 生成
```python
class PinListGenerator:
def __init__(self, pinmap: PinMAP, validation: ValidationResult)
def generate(self) -> PinList:
"""
生成规则:
- A1 = 封装信息
- A列 = PinName
- B列 = Pin序号
- 按 Pin序号 递增排序
"""
```
#### 模块6`xlsx_writer` — XLSX 输出引擎
```python
class XLSXWriter:
def __init__(self)
def write_pinlist(self, pinlist: PinList, output_path: str)
```
### 3.2 模块依赖关系
```
main.py
├── file_selector.py
├── xls_reader.py ──┐
├── xlsx_reader.py ─┤
│ ▼
│ pinmap_parser.py
│ ▼
│ validator.py
│ ▼
│ pinlist_generator.py
│ ▼
└─────────── xlsx_writer.py
```
---
## 4. 数据结构设计
### 4.1 Pin引脚
```python
@dataclass
class Pin:
number: int # 引脚序号1-based
name: str # 引脚名称(缺失时默认为 "NC"
edge: str # 所在边: "top" | "right" | "bottom" | "left"
position_on_edge: int # 在该边上的位置0-based
```
### 4.2 PinMAP引脚映射图
```python
@dataclass
class PinMAP:
package_info: str # A1 单元格封装信息
pins: list[Pin] # 所有引脚(按序号排序)
width: int # 方形宽度(列数)
height: int # 方形高度(行数)
grid_origin: tuple[int, int] # (row, col) 方形左上角
raw_cells: dict[tuple[int, int], str] # 原始单元格数据(调试用)
```
### 4.3 PinList引脚列表
```python
@dataclass
class PinList:
package_info: str # 输出 A1 单元格
rows: list[tuple[str, int]] # [(PinName, Pin序号), ...] 按序号排序
```
### 4.4 ValidationResult验证结果
```python
@dataclass
class ValidationError:
level: str # "error" | "warning"
message: str # 错误描述
details: str # 详细信息如重复的序号、缺失的Pin等
@dataclass
class ValidationResult:
is_valid: bool
errors: list[ValidationError]
warnings: list[ValidationError]
```
### 4.5 内部:单元格坐标体系
```
统一使用 (row, col) 元组0-based
- row 0 = Excel 第1行
- col 0 = Excel A列
- A1 = (0, 0)
- A2 = (1, 0)
- C2 = (1, 2)
- B4 = (3, 1)
```
---
## 5. 异常处理策略
### 5.1 异常分类
| 级别 | 类型 | 处理方式 | 示例 |
|------|------|---------|------|
| FATAL | 文件格式错误 | 终止 + 错误信息 | 非Excel文件、BIFF损坏 |
| FATAL | 结构错误 | 终止 + 错误信息 | 非方形、缺少A1、无边数据 |
| ERROR | 数据错误 | 终止 + 详细错误 | 序号不连续、序号重复 |
| WARN | 数据警告 | 提示 + 继续 | PinName缺失默认NC |
| INFO | 信息提示 | 仅显示 | 转换完成、统计信息 |
### 5.2 自定义异常层次
```python
class PinMapError(Exception):
"""基类异常"""
class FileFormatError(PinMapError):
"""文件格式错误非xls/xlsx、文件损坏"""
class StructureError(PinMapError):
"""PinMAP结构错误非方形、缺少必要数据"""
class ValidationError(PinMapError):
"""数据验证错误(序号不连续、重复)"""
class WarningLevel(PinMapError):
"""警告级别PinName缺失等可继续处理"""
```
### 5.3 错误信息规范
```
[级别] 错误类别: 具体描述
详细信息: ...
建议操作: ...
```
示例:
```
[ERROR] 序号不连续: 检测到序号间断
预期: 1,2,3,4,5,6 实际: 1,2,3,5,6
缺失序号: 4
建议: 检查PinMAP文件中是否有遗漏的引脚
[WARN] PinName缺失: 检测到 3 个引脚缺少PinName
缺失引脚: Pin 7, Pin 12, Pin 18
处理: 已自动设为 "NC"
```
### 5.4 主流程异常处理
```python
def main():
try:
filepath = select_file()
if not filepath:
return # 用户取消
cells = read_excel(filepath)
pinmap = parse_pinmap(cells)
result = validate(pinmap)
if result.has_errors():
print_errors(result.errors)
return
if result.has_warnings():
print_warnings(result.warnings)
if not confirm_continue():
return
pinlist = generate(pinmap, result)
output_path = build_output_path(filepath)
write_xlsx(pinlist, output_path)
print_success(output_path)
except FileFormatError as e:
print_fatal(f"文件格式错误: {e}")
except StructureError as e:
print_fatal(f"结构错误: {e}")
except ValidationError as e:
print_fatal(f"数据验证失败: {e}")
except Exception as e:
print_fatal(f"未知错误: {e}")
```
---
## 6. 文件处理流程图
```
┌─────────────────────────────────────────────────────────────────┐
│ 主流程 (main.py) │
└─────────────────────────────────────────────────────────────────┘
┌───────────────────────────┐
│ 1. 文件选择 (file_selector) │
│ - tkinter 文件对话框 │
│ - 过滤 *.xls, *.xlsx │
└───────────┬───────────────┘
┌───────────▼───────────────┐
│ 2. 读取 Excel 文件 │
│ ┌─────────────────────┐ │
│ │ 判断文件格式 │ │
│ │ .xls → xls_reader │ │
│ │ .xlsx → xlsx_reader│ │
│ └─────────┬───────────┘ │
│ ┌─────────▼───────────┐ │
│ │ 解析为单元格字典 │ │
│ │ {(row,col): value} │ │
│ └─────────┬───────────┘ │
└────────────┬──────────────┘
┌────────────▼──────────────┐
│ 3. PinMAP 解析 (pinmap_parser) │
│ ┌─────────────────────┐ │
│ │ ① 定位方形边界 │ │
│ │ 扫描非空单元格 │ │
│ │ 确定 width/height│ │
│ └─────────┬───────────┘ │
│ ┌─────────▼───────────┐ │
│ │ ② 提取 A1 封装信息 │ │
│ └─────────┬───────────┘ │
│ ┌─────────▼───────────┐ │
│ │ ③ 沿四边提取引脚 │ │
│ │ 上边 → 右边 │ │
│ │ 下边 → 左边 │ │
│ │ 逆时针排序 │ │
│ └─────────┬───────────┘ │
│ ┌─────────▼───────────┐ │
│ │ ④ 组装 PinMAP 对象 │ │
│ └─────────┬───────────┘ │
└────────────┬──────────────┘
┌────────────▼──────────────┐
│ 4. 数据验证 (validator) │
│ ┌─────────────────────┐ │
│ │ ✓ 序号连续性检查 │ │
│ │ ✓ 序号唯一性检查 │ │
│ │ ✓ PinName 缺失检查 │ │
│ │ ✓ 方形结构完整性 │ │
│ └─────────┬───────────┘ │
│ ┌─────────▼───────────┐ │
│ │ ERROR → 终止流程 │ │
│ │ WARN → 提示确认 │ │
│ └─────────┬───────────┘ │
└────────────┬──────────────┘
┌────────────▼──────────────┐
│ 5. PinList 生成 (generator) │
│ ┌─────────────────────┐ │
│ │ A1 = 封装信息 │ │
│ │ A列 = PinName │ │
│ │ B列 = Pin序号 │ │
│ │ 按序号递增排序 │ │
│ └─────────┬───────────┘ │
└────────────┬──────────────┘
┌────────────▼──────────────┐
│ 6. XLSX 输出 (xlsx_writer) │
│ ┌─────────────────────┐ │
│ │ 构建 OOXML 结构 │ │
│ │ [Content_Types].xml │ │
│ │ xl/workbook.xml │ │
│ │ xl/sharedStrings.xml│ │
│ │ xl/worksheets/ │ │
│ │ sheet1.xml │ │
│ └─────────┬───────────┘ │
│ ┌─────────▼───────────┐ │
│ │ ZIP 打包输出 │ │
│ └─────────┬───────────┘ │
└────────────┬──────────────┘
┌───────────────────────────┐
│ 完成!输出 .xlsx 文件 │
│ 默认命名: {原文件名}_PinList.xlsx │
└───────────────────────────┘
```
---
## 7. PinMAP 结构详解
### 7.1 坐标映射
以 4×4 方形为例width=4, height=4
```
col A(0) col B(1) col C(2) col D(3)
row 0 [A1=封装] [PinName] [PinName] [PinName] ← 上边PinName行
row 1 [1] [2] [3] [4] ← 上边Pin序号行
row 2 [PinName] [ ] [PinName] ← 中间区域(留空)
row 3 [PinName] [ ] [PinName] ← 中间区域(留空)
row 4 [13] [12] [11] [10] ← 下边Pin序号行
[PinName] [PinName] [PinName] [PinName] ← 下边PinName行行5
↑ ↑ ↑ ↑
左边 左边 右边 右边
PinName PinName PinName PinName
(列前) (列前) (列后) (列后)
```
**实际引脚分布**
- 上边Pin 1(A,row1) → Pin 2(B,row1) → Pin 3(C,row1) → Pin 4(D,row1)
- 右边Pin 5(D,row2) → Pin 6(D,row3) → Pin 7(D,row4)
- 下边Pin 8(D,row4) ... 等等
等等,让我重新理清。根据需求描述:
```
C2 是上边最后一个Pin序号C3是对应PinName
A4 是左边第一个Pin序号B4是对应PinName
```
这说明:
- 行1Excel第2行= 上边Pin序号行
- 行2Excel第3行= 上边PinName行
- 行3Excel第4行= 左边第一个Pin序号行
所以方形区域从 row=1 开始Excel第2行row=0 是PinName行。
### 7.2 四边提取规则(修正版)
设方形区域:行范围 [r_top, r_bottom],列范围 [c_left, c_right]
```
上边 (Top Edge)
Pin序号位置row=r_top, col=c_left → c_right从左到右
PinName位置row=r_top-1, col=c_left → c_right
右边 (Right Edge)
Pin序号位置col=c_right, row=r_top → r_bottom从上到下
PinName位置col=c_right+1, row=r_top → r_bottom
下边 (Bottom Edge)
Pin序号位置row=r_bottom, col=c_right → c_left从右到左
PinName位置row=r_bottom+1, col=c_right → c_left
左边 (Left Edge)
Pin序号位置col=c_left, row=r_bottom → r_top从下到上
PinName位置col=c_left-1, row=r_bottom → r_top即B列当c_left=0时
```
### 7.3 角点共享规则
```
左上角 (c_left, r_top) = 上边起点 = 左边终点 → Pin 1
右上角 (c_right, r_top) = 上边终点 = 右边起点
右下角 (c_right, r_bottom) = 右边终点 = 下边起点
左下角 (c_left, r_bottom) = 下边终点 = 左边终点
总Pin数 = 2 × width + 2 × height - 4
```
### 7.4 长方形支持
```
非正方形示例width=6, height=4
总Pin数 = 2×6 + 2×4 - 4 = 16
上边6个引脚1-6
右边3个引脚7-9
下边5个引脚10-14
左边3个引脚15-16回到Pin 1
验证6 + 3 + 5 + 2 = 16 ✓(左边排除两个角点)
```
---
## 8. 任务拆分建议
### 8.1 推荐拆分方案
建议拆分为 **3 个子任务**,由 2-3 个编码 Agent 并行开发:
#### 任务 AExcel 读写引擎(最复杂,优先开发)
**负责模块**`xls_reader.py`, `xlsx_reader.py`, `xlsx_writer.py`
**工作内容**
1. 实现 BIFF8 OLE2 解析器xls 读取)
2. 实现 ZIP+XML 解析器xlsx 读取)
3. 实现 OOXML 生成器xlsx 写入)
4. 统一接口:`read_excel(filepath) → dict[(row,col), str]`
5. 编写单元测试(用已知 xls/xlsx 文件验证)
**预估工作量**BIFF8 解析是最大难点)
**关键风险**
- BIFF8 变体多BIFF5/BIFF8 混用、不同 Unicode 编码)
- 需要大量测试文件验证
#### 任务 BPinMAP 解析与验证(核心业务逻辑)
**负责模块**`pinmap_parser.py`, `validator.py`
**工作内容**
1. 实现方形边界检测算法
2. 实现四边引脚提取逻辑
3. 实现角点共享处理
4. 实现验证规则(连续性、唯一性、完整性)
5. 编写单元测试(模拟各种 PinMAP 布局)
**预估工作量**:中
**关键风险**
- 边界条件处理(长方形 vs 正方形、最小尺寸)
- 角点共享逻辑的正确性
#### 任务 C流程编排与输出集成层
**负责模块**`main.py`, `file_selector.py`, `pinlist_generator.py`
**工作内容**
1. 实现文件选择对话框
2. 实现 PinList 数据转换
3. 实现输出文件命名和保存
4. 实现主流程异常处理和用户提示
5. 端到端集成测试
**预估工作量**:低
**关键风险**
- tkinter 在 Windows 上的兼容性
- 用户交互流程的友好性
### 8.2 开发顺序
```
第1轮任务 AExcel 读写引擎)
↓ 完成后
第2轮任务 BPinMAP 解析与验证)
↓ 完成后
第3轮任务 C流程编排与输出
↓ 完成后
集成测试 → 发布
```
### 8.3 接口契约(模块间约定)
```python
# xls_reader / xlsx_reader 统一接口
def read_excel_cells(filepath: str) -> dict[tuple[int, int], str]:
"""
输入: Excel 文件路径
输出: {(row, col): str} 单元格字典
约定: row/col 从 0 开始,所有值转为 str
"""
# pinmap_parser 接口
def parse_pinmap(cells: dict[tuple[int, int], str]) -> PinMAP:
"""
输入: 单元格字典
输出: PinMAP 对象
约定: 结构错误时抛出 StructureError
"""
# validator 接口
def validate_pinmap(pinmap: PinMAP) -> ValidationResult:
"""
输入: PinMAP 对象
输出: ValidationResult
约定: 不抛出异常,所有问题记录在 ValidationResult 中
"""
# pinlist_generator 接口
def generate_pinlist(pinmap: PinMAP, validation: ValidationResult) -> PinList:
"""
输入: PinMAP + ValidationResult
输出: PinList 对象
约定: 自动处理 WARN 级别的缺失 PinName设为 NC
"""
# xlsx_writer 接口
def write_pinlist_xlsx(pinlist: PinList, output_path: str):
"""
输入: PinList + 输出路径
输出: 无(写入文件)
约定: 自动创建父目录
"""
```
---
## 9. 项目目录结构
```
pinmap-to-pinlist/
├── Code/
│ ├── src/
│ │ ├── __init__.py
│ │ ├── main.py # 入口点
│ │ ├── file_selector.py # 文件选择
│ │ ├── xls_reader.py # XLS 读取引擎
│ │ ├── xlsx_reader.py # XLSX 读取引擎
│ │ ├── pinmap_parser.py # PinMAP 解析
│ │ ├── validator.py # 数据验证
│ │ ├── pinlist_generator.py # PinList 生成
│ │ ├── xlsx_writer.py # XLSX 写入引擎
│ │ └── models.py # 数据模型定义
│ └── docs/
│ └── architecture-design.md # 本文档
├── Test/
│ ├── fixtures/ # 测试用 Excel 文件
│ │ ├── sample_4x4.xls
│ │ ├── sample_4x4.xlsx
│ │ ├── sample_rect.xls
│ │ └── ...
│ └── test_*.py # 单元测试
└── Releases/
└── pinmap2pinlist.exe # 打包后的可执行文件
```
---
## 10. 风险与缓解
| 风险 | 影响 | 概率 | 缓解措施 |
|------|------|------|---------|
| BIFF8 格式变体导致解析失败 | 高 | 中 | 收集多种 xls 样本测试;优先实现 BIFF8 最常见子集 |
| tkinter 在无头环境不可用 | 中 | 低 | 回退到命令行参数模式 |
| xlsx 写入的 XML 结构不兼容老版本 Excel | 中 | 低 | 遵循 OOXML 标准,使用最小兼容集 |
| 超大文件(>1000引脚性能问题 | 低 | 低 | 当前场景引脚数通常 <100无需优化 |
---
## 11. 附录
### A. BIFF8 记录类型速查
| 记录码 | 名称 | 说明 |
|--------|------|------|
| 0x0009 | BOF | 块起始 |
| 0x000A | EOF | 文件结束 |
| 0x00FD | LABELSST | 共享字符串表引用单元格 |
| 0x0203 | NUMBER | 浮点数单元格 |
| 0x0006 | FORMULA | 公式单元格 |
| 0x000C | RK | RK 数值 |
| 0x00FC | STRING | 公式字符串结果 |
| 0x0034 | SST | 全局共享字符串表 |
| 0x0042 | BOUNDSHEET | 工作表信息 |
| 0x00E0 | EXTSST | 扩展共享字符串表 |
### B. OOXML xlsx 目录结构
```
example.xlsx (ZIP)
├── [Content_Types].xml
├── _rels/
│ └── .rels
├── xl/
│ ├── workbook.xml
│ ├── _rels/
│ │ └── workbook.xml.rels
│ ├── sharedStrings.xml
│ ├── styles.xml
│ └── worksheets/
│ ├── sheet1.xml
│ └── sheet2.xml
└── docProps/
├── core.xml
└── app.xml
```
### C. 列字母 ↔ 索引转换
```python
def col_letter_to_index(letter: str) -> int:
"""A→0, B→1, ..., Z→25, AA→26, AB→27, ..."""
result = 0
for ch in letter.upper():
result = result * 26 + (ord(ch) - ord('A') + 1)
return result - 1
def col_index_to_letter(index: int) -> str:
"""0→A, 1→B, ..., 25→Z, 26→AA, ..."""
result = ""
index += 1
while index > 0:
index -= 1
result = chr(index % 26 + ord('A')) + result
index //= 26
return result
```
---
*文档结束 — 请审批后进入编码阶段*