258 lines
8.8 KiB
Markdown
258 lines
8.8 KiB
Markdown
# 技术文档:`xlsx_data_processor.py`
|
||
|
||
---
|
||
|
||
## 概述
|
||
|
||
该模块提供了一个用于处理 Excel(`.xlsx`)文件和结构化数据的工具集,支持从 `.xlsx` 文件中读取数据并将其转换为结构化的字典对象。它特别适用于管理具有特定格式规范的 CRUD(创建、读取、更新、删除)配置表,并能自动解析字段类型、主键、外键、索引等元信息。
|
||
|
||
模块主要功能包括:
|
||
- 从 `.xlsx` 文件加载数据并按工作表组织。
|
||
- 支持自定义字段类型的自动转换(如 `int`, `float`, `json` 等)。
|
||
- 支持扩展的数据格式(如 `cruddata`, `xlsxdata`)通过字符串协议调用。
|
||
- 提供对特殊格式的 CRUD 配置文件的支持(包含 `summary`, `fields`, `validation` 等 sheet)。
|
||
- 命令行接口,可批量合并多个数据源(Excel 和参数)输出 JSON。
|
||
|
||
---
|
||
|
||
## 依赖库
|
||
|
||
```python
|
||
import os
|
||
import sys
|
||
from traceback import print_exc
|
||
from openpyxl import load_workbook
|
||
from appPublic.myjson import loadf, dumpf, dumps
|
||
from appPublic.dictObject import DictObject
|
||
```
|
||
|
||
> ⚠️ 注意:`appPublic` 是一个自定义公共库,需确保已安装或路径正确。
|
||
|
||
---
|
||
|
||
## 核心类与函数说明
|
||
|
||
### 1. `TypeConvert` 类
|
||
|
||
负责将原始值根据指定类型进行安全转换。
|
||
|
||
#### 方法列表:
|
||
|
||
| 方法 | 描述 |
|
||
|------|------|
|
||
| `conv(typ, v)` | 根据类型名 `typ` 调用对应的转换方法(如 `to_int`),若无则返回原值。 |
|
||
| `to_int(v)` | 转换为整数,失败返回 `0`。 |
|
||
| `to_float(v)` | 转换为浮点数,失败返回 `0.0`。 |
|
||
| `to_str(v)` | 转换为字符串,失败返回空串 `''`。 |
|
||
| `to_json(v)` | 将字符串解析为 JSON 对象,空字符串保持不变,失败返回原值。⚠️ 存在 bug:应使用 `loads` 但代码中写成了 `loads(v)` 实际未导入。 |
|
||
| `to_date(v)`, `to_time(v)`, `to_timestamp(v)` | 占位方法,直接返回原值(未来可扩展)。 |
|
||
| `to_cruddata(v)` | 解析形如 `"cruddata:\"filename\".property"` 的字符串,动态加载 `CRUDData` 并执行属性/方法访问。 |
|
||
| `to_xlsxdata(v)` | 类似 `to_cruddata`,但用于普通 `XLSXData` 对象。 |
|
||
|
||
> ✅ 示例输入:`"cruddata:\"user.xlsx\".users"` → 加载 `user.xlsx` 并获取其 `users` 表数据。
|
||
|
||
> ❗ Bug 提示:`to_json` 中使用了未定义的 `loads` 函数,应改为 `from json import loads` 或使用 `dumps` 所属模块中的 `loads`。
|
||
|
||
---
|
||
|
||
### 2. `CRUDException(Exception)` 类
|
||
|
||
自定义异常类,用于报告 `.xlsx` 文件处理过程中的错误。
|
||
|
||
#### 属性:
|
||
- `xlsfile`: 出错的文件名
|
||
- `errmsg`: 错误描述
|
||
|
||
#### `__str__()` 返回格式:
|
||
```
|
||
filename:<文件名> error:<错误信息>
|
||
```
|
||
|
||
---
|
||
|
||
### 3. `XLSXData` 类
|
||
|
||
通用 `.xlsx` 文件读取器,将每个工作表解析为记录列表,并封装成 `DictObject`。
|
||
|
||
#### 构造函数:`__init__(xlsxfile, book=None)`
|
||
- `xlsxfile`: Excel 文件路径
|
||
- `book`: 可选的已打开的 `Workbook` 对象(用于性能优化或复用)
|
||
|
||
#### 主要方法:
|
||
|
||
| 方法 | 功能 |
|
||
|------|------|
|
||
| `_read()` | 内部方法,遍历所有 sheet 并调用 `readRecords()` 构建 `self.data`。 |
|
||
| `get_data()` | 返回最终解析后的数据对象(`DictObject`)。 |
|
||
| `readRecords(name, sheet)` | 读取单个 sheet 数据:<br> - 第一行作为字段定义(格式:`字段名:类型`)<br> - 后续行为数据行<br> - 使用 `TypeConvert` 进行类型转换<br> - 返回 `{name: [DictObject(...), ...]}` |
|
||
| `getFieldNames(row)` | 处理首行字段:<br> - 若为空则命名为 `F_i`<br> - 分割 `name:type` 形式,缺失类型设为 `None`<br> - 返回 `[ [字段名, 类型], ... ]` |
|
||
|
||
> 📌 字段命名规则示例:
|
||
> - `id:int` → 名称 `id`,类型 `int`
|
||
> - `username` → 名称 `username`,类型 `None`
|
||
> - 空单元格 → 自动命名为 `F_0`, `F_1`...
|
||
|
||
---
|
||
|
||
### 4. `CRUDData(XLSXData)` 类
|
||
|
||
继承自 `XLSXData`,专用于处理符合 CRUD 元数据规范的 `.xlsx` 文件。
|
||
|
||
#### 规范要求:
|
||
必须包含以下三个工作表:
|
||
- `summary`: 表基本信息(如主键)
|
||
- `fields`: 字段定义列表
|
||
- `validation`: 验证规则(外键、索引等)
|
||
|
||
#### 类方法:`isMe(book)`
|
||
判断给定 workbook 是否满足 CRUDData 格式(检查是否存在上述三个 sheet)。
|
||
|
||
#### 重写 `_read()` 方法流程:
|
||
1. 调用父类 `_read()` 解析所有 sheet。
|
||
2. 验证 `summary`, `fields`, `validation` 存在且唯一。
|
||
3. 执行元数据转换:
|
||
- `convPrimary()`: 解析主键字段(逗号分隔)
|
||
- `convForeignkey()`: 解析外键规则(格式:`table:value:title`)
|
||
- `convIndex()`: 解析索引定义(格式:`idx_type:key1,key2`)
|
||
- `check_codes_fields()`: 验证 `codes` 表中的字段是否存在于 `fields`
|
||
|
||
#### 辅助方法:
|
||
| 方法 | 功能 |
|
||
|------|------|
|
||
| `check_field(fieldname)` | 检查字段是否在 `fields` 表中存在 |
|
||
| `getFieldByNmae(fields, name)` | 获取指定名称的字段定义(拼写错误:`Nmae` 应为 `Name`)❗ |
|
||
| `getFKs(validation)` | 提取所有外键规则(`oper == 'fk'`) |
|
||
| `getIDXs(validation)` | 提取所有索引规则(`oper == 'idx'`) |
|
||
|
||
> ⚠️ Bug 提示:
|
||
> - `getFieldByNmae` 函数名拼写错误,可能导致调用失败。
|
||
> - `getFKs` 中条件判断写成了 `'oepr'` 而非 `'oper'`,导致无法识别外键。
|
||
|
||
---
|
||
|
||
### 5. `xlsxFactory(xlsxfilename)` 函数
|
||
|
||
根据文件内容自动选择合适的类实例化(工厂模式)。
|
||
|
||
#### 流程:
|
||
1. 打开 `.xlsx` 文件。
|
||
2. 遍历 `XLSXData` 的所有子类(递归查找),调用 `isMe(book)` 判断是否匹配。
|
||
3. 匹配成功则返回 `CRUDData` 实例,否则返回默认 `XLSXData` 实例。
|
||
4. 异常捕获并打印堆栈。
|
||
|
||
> ✅ 支持插件式扩展:可通过新增子类实现其他专用格式识别。
|
||
|
||
---
|
||
|
||
### 6. `ValueConvert(s)` 函数
|
||
|
||
解析特殊前缀字符串,支持外部资源引用。
|
||
|
||
| 前缀 | 行为 |
|
||
|------|------|
|
||
| `xlsfile::path/to/file.xlsx` | 使用 `xlsxFactory` 加载并返回 `.get_data()` |
|
||
| `jsonfile::path/to/data.json` | 使用 `loadf()` 读取 JSON 文件 |
|
||
| 其他 | 直接返回原字符串 |
|
||
|
||
---
|
||
|
||
### 7. `paramentHandle(ns)` 函数
|
||
|
||
对参数字典中的每个值应用 `ValueConvert`,实现参数注入时的自动资源加载。
|
||
|
||
> ✅ 示例:
|
||
```bash
|
||
python script.py data=xlsfile::config.xlsx user=jsonfile::user.json
|
||
```
|
||
→ 自动加载 Excel 和 JSON 文件内容。
|
||
|
||
---
|
||
|
||
## 主程序入口(`if __name__ == '__main__':`)
|
||
|
||
支持命令行运行,用法如下:
|
||
|
||
### 参数格式:
|
||
- `key=value`:设置命名参数(支持 `xlsfile::`, `jsonfile::`)
|
||
- `filepath`:直接加载数据文件(仅限 `.xlsx`, `.xls`)
|
||
|
||
### 执行流程:
|
||
1. 解析命令行参数,分离 `ns`(命名空间)和 `datafiles`(文件列表)。
|
||
2. 处理 `ns` 中的值(调用 `paramentHandle`)。
|
||
3. 遍历 `datafiles`,使用 `xlsxFactory` 加载每个 Excel 文件。
|
||
4. 合并所有数据到 `retData`。
|
||
5. 输出合并后的 JSON 结果(使用 `dumps` 格式化)。
|
||
|
||
### 示例命令:
|
||
```bash
|
||
python xlsx_data_processor.py \
|
||
config=xlsfile::app_config.xlsx \
|
||
user=jsonfile::default_user.json \
|
||
roles.xlsx \
|
||
permissions.xlsx
|
||
```
|
||
|
||
输出结果为整合所有来源数据的 JSON 对象。
|
||
|
||
---
|
||
|
||
## 已知问题与改进建议
|
||
|
||
| 问题 | 描述 | 建议修复 |
|
||
|------|------|----------|
|
||
| `to_json` 使用 `loads` 未定义 | 导致 JSON 解析失败 | 改为 `from json import loads` 或使用 `dumps.loads()` |
|
||
| `getFieldByNmae` 拼写错误 | 方法名错误 | 更正为 `getFieldByName` |
|
||
| `getFKs` 条件判断错误 | `'oepr' == 'fk'` 永不成立 | 改为 `'oper'` |
|
||
| `check_field(f for f in ...)` | 语法错误,不能传生成器 | 改为循环逐一检查每个字段 |
|
||
| `vs < 3` 在 `split` 后无效 | `vs` 是 list,比较 `< 3` 不合理 | 应改为 `len(vs) < 3` |
|
||
| `to_cruddata` / `to_xlsxdata` 中 `vs[2] is None` | `split` 不会返回 `None` 元素 | 应改为 `len(vs) <= 2` 判断是否有命令部分 |
|
||
|
||
---
|
||
|
||
## 使用示例
|
||
|
||
### 示例 Excel 结构(`users.xlsx`)
|
||
|
||
**Sheet: users**
|
||
```
|
||
id:int,name:str,age:int
|
||
1,Alice,25
|
||
2,Bob,30
|
||
```
|
||
|
||
**Sheet: summary**
|
||
```
|
||
table:primary
|
||
users:id
|
||
```
|
||
|
||
**Sheet: fields**
|
||
```
|
||
name,type,desc
|
||
id,int,用户ID
|
||
name,str,用户名
|
||
age,int,年龄
|
||
```
|
||
|
||
### 调用方式:
|
||
|
||
```python
|
||
d = xlsxFactory('users.xlsx')
|
||
data = d.get_data()
|
||
print(data.users[0].name) # 输出: Alice
|
||
print(data.summary[0].primary) # 输出: ['id']
|
||
```
|
||
|
||
---
|
||
|
||
## 总结
|
||
|
||
本模块是一个轻量级但功能丰富的 `.xlsx` 数据处理器,适合用于配置中心、元数据管理、自动化脚本等场景。结合自定义前缀协议和工厂模式,具备良好的扩展性。
|
||
|
||
尽管存在少量 bug 和命名问题,整体设计清晰,层次分明,易于维护和二次开发。
|
||
|
||
---
|
||
|
||
> **作者**:Auto-generated Technical Documentation
|
||
> **版本**:1.0
|
||
> **最后更新**:2025-04-05 |