Files
xls_doc/HOWTO.md
2026-06-24 09:45:35 +08:00

726 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.
# docfill HOWTO
本文档说明如何使用 `docfill` 完成本地文档自动填充。文档分为两部分:
- “最小实践”用于快速完成一次可验证运行。
- “详细说明”用于理解任务配置、运行变量、筛选、模板、输出、错误和日志。
## 一、最小实践
### 1. 进入项目目录
```bash
cd /Users/zhaoyilun/Documents/xls_doc
```
### 2. 构建命令行工具
```bash
bash scripts/build.sh
```
输出示例:
```text
/Users/zhaoyilun/Documents/xls_doc/dist/docfill-darwin-arm64
```
构建后的可执行文件是:
```text
dist/docfill-darwin-arm64
```
### 3. 准备测试素材
仓库中已经包含 `manual_tests/` 测试素材。如果需要重新生成,可执行:
```bash
/Users/zhaoyilun/.cache/codex-runtimes/codex-primary-runtime/dependencies/python/bin/python3 scripts/make-manual-test-materials.py
```
输出示例:
```text
/Users/zhaoyilun/Documents/xls_doc/manual_tests
```
测试素材包含四类任务:
- `manual_tests/01_excel_to_word`: Excel 源数据生成 Word 日报。
- `manual_tests/02_sqlite_to_excel`: SQLite 源数据生成 Excel 汇总表。
- `manual_tests/03_excel_to_excel`: Excel 源数据生成 Excel 质检表。
- `manual_tests/04_filter_work_order_to_word`: 按日期和工单编号筛选一条工单,再生成 Word。
### 4. 运行筛选工单案例
命令:
```bash
dist/docfill-darwin-arm64 run -c manual_tests/04_filter_work_order_to_word/task.json --var date=2026-06-18 --var ticket_no=GD-002 --verbose
```
输出示例:
```text
log: load config path=manual_tests/04_filter_work_order_to_word/task.json
log: read data type=excel path=/Users/zhaoyilun/Documents/xls_doc/manual_tests/04_filter_work_order_to_word/source_work_orders.xlsx
log: read data type=excel path=/Users/zhaoyilun/Documents/xls_doc/manual_tests/04_filter_work_order_to_word/source_work_orders.xlsx rows=3
log: filter rows=3 matched=1 expect=one
log: render row=1 output=/Users/zhaoyilun/Documents/xls_doc/manual_tests/04_filter_work_order_to_word/out/work_order_2026-06-18_GD-002.docx
generated 1 file(s)
- /Users/zhaoyilun/Documents/xls_doc/manual_tests/04_filter_work_order_to_word/out/work_order_2026-06-18_GD-002.docx
```
查看输出目录:
```bash
open manual_tests/04_filter_work_order_to_word/out
```
输入 Excel 内容示例:
```text
日期 工单编号 客户名称 系统来源 处理结果 下一步
2026-06-18 GD-001 A 公司 PMS3.0 已派单 等待现场反馈
2026-06-18 GD-002 B 公司 D5000 已完成 归档并同步日报
2026-06-19 GD-001 C 公司 风控平台 待复核 补充截图
```
本次命令传入:
```text
date = 2026-06-18
ticket_no = GD-002
```
所以工具会筛选:
```text
日期 = 2026-06-18并且 工单编号 = GD-002
```
最终只生成 `GD-002` 对应的 Word 文件。
## 二、详细说明
### 1. task.json 与 --var 的关系
`task.json` 是某一种业务报表的固定任务说明书。它通常由开发人员或维护人员准备好,普通运行时不需要频繁修改。
命令行 `--var` 是本次运行传入的具体条件,例如日期、工单编号、人员、批次。
可以这样理解:
```text
不同业务 = 不同 task.json
本次运行的具体条件 = --var
真实业务数据 = Excel 或 SQLite
输出模板 = Word 或 Excel
```
例如不同业务可以分别维护:
```text
工单日报.json
部门汇总.json
质检报告.json
```
运行时只传本次变化的信息:
```bash
dist/docfill-darwin-arm64 run -c 工单日报.json --var date=2026-06-18 --var ticket_no=GD-002
```
变量优先级:
```text
数据表里的字段 > 命令行 --var > task.json 里的 vars
```
示例:
```json
"vars": {
"date": "2026-06-18"
}
```
如果运行时执行:
```bash
dist/docfill-darwin-arm64 run -c task.json --var date=2026-06-19
```
实际使用的是:
```text
date = 2026-06-19
```
也就是命令行 `--var` 会覆盖 `task.json` 中的同名默认变量。
### 2. 命令格式
`docfill` 当前只提供一个核心命令:
```bash
dist/docfill-darwin-arm64 run -c <task.json> [--var key=value] [--verbose]
```
参数说明:
| 参数 | 作用 | 示例 |
| --- | --- | --- |
| `run` | 执行一次填充任务 | `dist/docfill-darwin-arm64 run -c task.json` |
| `-c` | 指定任务配置文件 | `-c manual_tests/04_filter_work_order_to_word/task.json` |
| `--config` | `-c` 的完整写法 | `--config task.json` |
| `--var` | 传入运行时变量,可以重复使用 | `--var date=2026-06-18 --var ticket_no=GD-002` |
| `--verbose` | 输出诊断日志到 stderr | `--verbose` |
命令行参数错误示例:
```bash
dist/docfill-darwin-arm64 run --var date
```
输出示例:
```text
docfill: [CLI_USAGE] invalid value "date" for flag -var: invalid --var "date", want key=value
hint: Check command flags and use key=value for --var.
```
### 3. 任务配置结构
典型的筛选任务配置如下:
```json
{
"vars": {
"date": "2026-06-18"
},
"data": {
"type": "excel",
"path": "./source_work_orders.xlsx",
"sheet": "工单",
"header_row": 1
},
"filter": {
"equals": {
"日期": "{{date}}",
"工单编号": "{{ticket_no}}"
},
"expect": "one"
},
"template": {
"path": "./work_order_template.docx"
},
"output": {
"path": "./out/work_order_{{日期}}_{{工单编号}}.docx",
"overwrite": true
}
}
```
配置字段说明:
| 字段 | 必填 | 说明 |
| --- | --- | --- |
| `vars` | 否 | 默认变量。适合放日期、批次、班次等默认值。 |
| `data.type` | 是 | 数据源类型。支持 `excel``xlsx``sqlite`。 |
| `data.path` | 是 | 数据源路径。支持 `.xlsx` 或 SQLite `.db`。 |
| `data.sheet` | Excel 可选 | Excel 工作表名称。未填写时读取第一个工作表。 |
| `data.header_row` | Excel 可选 | Excel 表头行号,从 `1` 开始。未填写时默认为 `1`。 |
| `data.query` | SQLite 必填 | SQLite 查询语句。查询结果列名作为字段名。 |
| `data.params` | SQLite 可选 | SQL `?` 参数列表,支持 `{{变量名}}`。 |
| `filter.equals` | 否 | 等值筛选条件。左侧是数据字段,右侧是匹配值。 |
| `filter.expect` | 否 | 筛选结果数量要求。支持 `one``many`,默认 `one`。 |
| `template.path` | 是 | Word 或 Excel 模板路径。支持 `.docx``.xlsx`。 |
| `output.path` | 是 | 输出文件路径模板。可以使用 `{{字段名}}`。 |
| `output.overwrite` | 否 | 是否覆盖已存在文件。默认是 `false`。 |
相对路径规则:
```text
task.json 所在目录就是相对路径的基准目录。
```
### 4. 业务筛选 filter
`filter` 的作用是从数据表中筛选出本次要使用的数据行。它的目标不是提供数据库查询能力,而是提供符合表格使用习惯的简单筛选。
配置示例:
```json
"filter": {
"equals": {
"日期": "{{date}}",
"工单编号": "{{ticket_no}}"
},
"expect": "one"
}
```
含义:
```text
找到 日期 等于 date并且 工单编号 等于 ticket_no 的数据行。
```
命令:
```bash
dist/docfill-darwin-arm64 run -c task.json --var date=2026-06-18 --var ticket_no=GD-002
```
实际筛选:
```text
日期 = 2026-06-18
工单编号 = GD-002
```
筛选规则:
- `filter.equals` 左侧必须是 Excel 表头或 SQLite 查询结果列名。
- `filter.equals` 右侧可以写固定值,也可以写 `{{变量名}}`
- 多个条件之间是“全部满足”关系。
- 比较前会去掉单元格值和筛选值两侧的空格。
- 只支持等值匹配。
- 不支持模糊匹配、范围查询、OR、排序表达式或复杂条件。
`filter.expect` 支持两个值:
| 值 | 含义 | 适用场景 |
| --- | --- | --- |
| `one` | 必须只匹配一行 | 工单、单份日报、单个报表。 |
| `many` | 允许匹配多行 | 按日期批量生成多个人或多个部门的报表。 |
未填写 `filter.expect` 时,默认按 `one` 处理。
如果没有配置 `filter`,工具会沿用旧行为:读取到的每一行都会生成一个输出文件。
复杂情况的处理建议:
- 如果多个字段才能确定一条数据,就在 `filter.equals` 中写多个条件。
- 如果表格本身不容易筛选,建议在 Excel 中新增辅助列,例如 `填报批次``业务唯一标识``筛选日期`
- 不建议把复杂业务规则写进 `docfill`,复杂规则应在数据准备阶段完成。
### 5. Excel 数据源
Excel 数据源配置示例:
```json
{
"data": {
"type": "excel",
"path": "./source_work_orders.xlsx",
"sheet": "工单",
"header_row": 1
}
}
```
运行示例:
```bash
dist/docfill-darwin-arm64 run -c manual_tests/04_filter_work_order_to_word/task.json --var date=2026-06-18 --var ticket_no=GD-002
```
Excel 读取规则:
- `header_row` 指定表头行,表头单元格内容就是字段名。
- 表头下方的每一行是候选数据。
- 配置了 `filter` 时,先筛选,再生成输出。
- 未配置 `filter` 时,每一行都会生成一个输出文件。
- 空行会被跳过。
- 空表头列会被忽略。
表头在第 1 行的输入示例:
```text
日期 工单编号 客户名称 系统来源 处理结果 下一步
2026-06-18 GD-001 A 公司 PMS3.0 已派单 等待现场反馈
2026-06-18 GD-002 B 公司 D5000 已完成 归档并同步日报
2026-06-19 GD-001 C 公司 风控平台 待复核 补充截图
```
表头在第 2 行的配置示例:
```json
{
"data": {
"type": "excel",
"path": "./source_audit_data.xlsx",
"sheet": "Audit",
"header_row": 2
}
}
```
对应输入示例:
```text
日报质检导出
employee date item score comment
王五 2026-06-18 日报完整性 92 字段齐全,说明清楚
赵六 2026-06-18 系统截图一致性 87 缺少 1 张截图,需要补充
```
### 6. SQLite 数据源
SQLite 数据源配置示例:
```json
{
"vars": {
"date": "2026-06-18"
},
"data": {
"type": "sqlite",
"path": "./source_summary.db",
"query": "select department, report_date, total_count, abnormal_count, owner from department_summary order by department",
"params": []
},
"filter": {
"equals": {
"report_date": "{{date}}"
},
"expect": "many"
}
}
```
运行示例:
```bash
dist/docfill-darwin-arm64 run -c manual_tests/02_sqlite_to_excel/task.json --var date=2026-06-18
```
SQLite 读取规则:
- `data.query` 的查询结果列名就是模板字段名。
- `data.params` 按顺序对应 SQL 中的 `?`
- `data.params` 可以引用 `vars` 或命令行 `--var`
- 配置了 `filter`SQL 查询结果还会再经过业务筛选。
- 查询结果的每一行或筛选后的每一行生成一个输出文件。
输入数据示例:
```text
department report_date total_count abnormal_count owner
营销部 2026-06-17 115 2 张三
客服部 2026-06-18 76 0 李四
营销部 2026-06-18 128 3 张三
```
SQLite 可以继续使用 SQL 参数:
```json
"data": {
"type": "sqlite",
"path": "./source_summary.db",
"query": "select department, report_date, total_count, abnormal_count, owner from department_summary where report_date = ? order by department",
"params": ["{{date}}"]
}
```
这适合由开发人员维护的 SQLite 数据源。面向表格使用习惯时,优先使用 `filter.equals` 表达业务筛选。
### 7. Word 模板
Word 模板使用 `.docx` 文件,模板中写入 `{{字段名}}`
```text
日期:{{日期}}
工单编号:{{工单编号}}
客户名称:{{客户名称}}
系统来源:{{系统来源}}
处理结果:{{处理结果}}
下一步:{{下一步}}
```
运行示例:
```bash
dist/docfill-darwin-arm64 run -c manual_tests/04_filter_work_order_to_word/task.json --var date=2026-06-18 --var ticket_no=GD-002
```
生成结果示例:
```text
日期2026-06-18
工单编号GD-002
客户名称B 公司
系统来源D5000
处理结果:已完成
下一步:归档并同步日报
```
Word 支持同一段落内被 Word 拆分的文本占位符。例如模板内部可能把 `{{name}}` 拆成相邻文本节点 `{{na``me}}`,工具会按同一段落进行最小兼容处理。
这种兼容会把同一段落内的拆分文本合并后再写回,渲染结果会写入该段落第一个文本节点的位置,后续拆分节点的独立格式不再体现。建议模板中每个占位符使用同一种字体、颜色和加粗状态,不要在 `{{`、字段名、`}}` 之间混用格式。
当前不支持复杂模板语法,例如表格循环、条件判断、图片插入、跨段落拼接占位符。
### 8. Excel 模板
Excel 模板使用 `.xlsx` 文件,单元格中写入 `{{字段名}}`
```text
B2: {{department}}
B3: {{report_date}}
B4: {{owner}}
B5: {{total_count}}
B6: {{abnormal_count}}
```
运行示例:
```bash
dist/docfill-darwin-arm64 run -c manual_tests/02_sqlite_to_excel/task.json --var date=2026-06-18
```
生成结果示例:
```text
B2: 营销部
B3: 2026-06-18
B4: 张三
B5: 128
B6: 3
```
Excel 模板会遍历所有工作表,替换单元格文本中的占位符。当前不支持公式重算、透视表刷新、复杂样式模板语法。
### 9. 输出路径
输出路径可以使用字段占位符:
```json
{
"output": {
"path": "./out/work_order_{{日期}}_{{工单编号}}.docx",
"overwrite": true
}
}
```
运行示例:
```bash
dist/docfill-darwin-arm64 run -c manual_tests/04_filter_work_order_to_word/task.json --var date=2026-06-18 --var ticket_no=GD-002
```
输出文件示例:
```text
work_order_2026-06-18_GD-002.docx
```
文件名清理规则:
```text
/ \ : * ? " < > |
```
以上字符会在输出路径字段值中替换为 `_`。例如:
```text
张三/测试 -> 张三_测试
```
覆盖规则:
- `output.overwrite` 未设置或为 `false` 时,输出文件已存在会失败。
- `output.overwrite``true` 时,允许覆盖同名输出文件。
### 10. 日志、标准输出和退出码
成功时stdout 输出生成文件清单:
```text
generated 1 file(s)
- /path/to/out/work_order_2026-06-18_GD-002.docx
```
启用 `--verbose`stderr 输出诊断日志:
```text
log: load config path=task.json
log: read data type=excel path=/path/data.xlsx
log: read data type=excel path=/path/data.xlsx rows=3
log: filter rows=3 matched=1 expect=one
log: render row=1 output=/path/out/work_order_2026-06-18_GD-002.docx
```
退出码:
| 退出码 | 含义 | 适用场景 |
| --- | --- | --- |
| `0` | 成功 | 可以读取 stdout 中的生成文件清单。 |
| `1` | 执行失败 | 配置、数据、模板、输出等处理失败。 |
| `2` | 命令行错误 | 参数缺失、命令错误、`--var` 格式错误。 |
数字员工或调度脚本建议:
- 判断退出码确认任务是否成功。
- 成功时读取 stdout 获取生成文件路径。
- 失败时记录 stderr用于定位错误码、提示和上下文。
- 仅在诊断或审计时启用 `--verbose`
### 11. 错误格式与错误码
错误输出格式:
```text
docfill: [ERROR_CODE] message
hint: repair suggestion
context: key=value
```
配置文件不存在示例:
```bash
dist/docfill-darwin-arm64 run -c manual_tests/not-exist.json
```
输出示例:
```text
docfill: [CONFIG_READ] read config: open manual_tests/not-exist.json: no such file or directory
hint: Check that the config file exists and is readable.
context: config=manual_tests/not-exist.json
```
筛选无匹配示例:
```bash
dist/docfill-darwin-arm64 run -c manual_tests/04_filter_work_order_to_word/task.json --var date=2026-06-18 --var ticket_no=GD-999 --verbose
```
输出示例:
```text
log: load config path=manual_tests/04_filter_work_order_to_word/task.json
log: read data type=excel path=/Users/zhaoyilun/Documents/xls_doc/manual_tests/04_filter_work_order_to_word/source_work_orders.xlsx
log: read data type=excel path=/Users/zhaoyilun/Documents/xls_doc/manual_tests/04_filter_work_order_to_word/source_work_orders.xlsx rows=3
docfill: [DATA_FILTER_NO_MATCH] filter matched no rows
hint: Check filter.equals, --var values, or source table contents.
context: expect=one filter=工单编号=GD-999,日期=2026-06-18
```
错误码说明:
| 错误码 | 含义 | 常见处理方式 |
| --- | --- | --- |
| `CLI_USAGE` | 命令行参数错误 | 检查命令、`-c``--var key=value`。 |
| `CONFIG_READ` | 配置文件读取失败 | 检查路径、文件是否存在、读权限。 |
| `CONFIG_PARSE` | 配置 JSON 解析失败 | 检查 JSON 语法。 |
| `CONFIG_VALIDATE` | 配置字段缺失或不支持 | 检查 `data.type``data.path``template.path``output.path``filter.expect`。 |
| `DATA_EXCEL` | Excel 数据读取失败 | 检查 `.xlsx` 文件、工作表名、表头行。 |
| `DATA_SQLITE` | SQLite 数据读取或查询失败 | 检查 `.db` 文件、SQL、参数。 |
| `DATA_EMPTY` | 数据源没有返回可生成的数据行 | 检查源数据是否为空。 |
| `DATA_FILTER_FIELD` | 筛选字段不在数据中 | 检查 Excel 表头或 SQLite 查询列名。 |
| `DATA_FILTER_VALUE` | 筛选条件引用的变量没有提供 | 补充 `--var``vars`。 |
| `DATA_FILTER_NO_MATCH` | 筛选后没有符合条件的数据行 | 检查筛选条件,或整理源表。 |
| `DATA_FILTER_MULTI_MATCH` | 期望一行但匹配多行 | 增加筛选条件,或整理重复数据。 |
| `TEMPLATE_OPEN` | 模板读取失败 | 检查模板路径和文件格式。 |
| `TEMPLATE_FIELD_MISSING` | 模板字段缺失 | 在数据源、`vars``--var` 中补充字段。 |
| `TEMPLATE_UNRESOLVED` | 渲染后仍残留占位符 | 检查模板占位符拼写和字段值。 |
| `OUTPUT_EXISTS` | 输出文件已存在 | 修改输出路径或设置 `output.overwrite: true`。 |
| `OUTPUT_WRITE` | 输出写入失败 | 检查输出目录权限、文件占用状态。 |
| `INTERNAL` | 未分类内部错误 | 保留 stderr 和输入文件,开启 `--verbose` 复现。 |
### 12. 自动化调用示例
以下示例展示数字员工或调度脚本如何调用 `docfill`。该脚本只做最小封装,不承担模板解析或业务逻辑。
```bash
#!/usr/bin/env bash
set -euo pipefail
DOCFILL="/Users/zhaoyilun/Documents/xls_doc/dist/docfill-darwin-arm64"
CONFIG="/Users/zhaoyilun/Documents/xls_doc/manual_tests/04_filter_work_order_to_word/task.json"
DATE="${1:?date is required, example: 2026-06-18}"
TICKET_NO="${2:?ticket_no is required, example: GD-002}"
"$DOCFILL" run -c "$CONFIG" --var date="$DATE" --var ticket_no="$TICKET_NO" --verbose
```
执行:
```bash
bash run-docfill.sh 2026-06-18 GD-002
```
预期结果:
```text
generated 1 file(s)
- /Users/zhaoyilun/Documents/xls_doc/manual_tests/04_filter_work_order_to_word/out/work_order_2026-06-18_GD-002.docx
```
集成建议:
- 把业务系统接口采集结果先落成 Excel 或 SQLite。
- 为每类报表维护一个 `task.json`
- 数字员工只负责准备运行变量并执行命令。
- 复杂筛选优先通过整理 Excel 表解决,例如新增辅助列。
- 不要把账号密码、审批状态、业务系统访问逻辑写入 `docfill` 配置。
### 13. 开发与验证
运行全部 Go 测试:
```bash
go test -count=1 ./...
```
运行冒烟测试:
```bash
bash scripts/smoke.sh
```
构建发布用本机二进制:
```bash
bash scripts/build.sh
```
重新生成手工测试素材:
```bash
/Users/zhaoyilun/.cache/codex-runtimes/codex-primary-runtime/dependencies/python/bin/python3 scripts/make-manual-test-materials.py
```
冒烟测试覆盖内容:
- Go 单元测试。
- 临时构建 `docfill`
- 生成临时 Excel、Word、SQLite 示例数据。
- 执行无筛选 Excel 到 Word 示例。
- 执行筛选 Excel 到 Word 示例。
- 执行 SQLite 到 Excel 示例。
- 检查输出文件是否生成。
### 14. 当前边界
为了保持工具小型、稳定、便于自动化调用,当前明确不包含以下能力:
- 不提供 Web 界面。
- 不提供模板设计器。
- 不直接登录业务系统。
- 不管理账号、密码或权限。
- 不实现审批流。
- 不实现 Word 表格循环或条件语法。
- 不实现 Excel 公式重算和透视表刷新。
- 不实现复杂筛选语法。
- 不实现复杂任务编排。
如需扩展,应优先通过外部脚本准备数据,再交给 `docfill` 执行单次文档填充。