# 技术文档：`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