Files
pinmap-to-pinlist/Code/docs/architecture-design.md
Agent 6b718f7af3 v1.0.0: PinMAP → PinList 转换器 首次发布
- 支持 .xls (BIFF8) 和 .xlsx 格式
- GUI 文件选择 + 命令行双模式
- 智能结构验证(重复/间隙/空单元格检测)
- 逆时针 PinMAP → 顺时针 PinList 自动转换
- Python 标准库,零第三方依赖
2026-05-25 13:27:08 +08:00

861 lines
28 KiB
Markdown
Raw 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.
# 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
```
---
*文档结束 — 请审批后进入编码阶段*