office-xlsx · git:20261021.536ea09 · 2026-10-21 · sha256 f2bcd6019bcc55aa
office-xlsx git:20261021.536ea09A
Immutable. This exact content never changes and is served at /api/v1/blob/f2bcd6019bcc55aa.
---
name: office-xlsx
description: 表格文件是主要输入或输出时使用。包括:打开、读取、修改或修复已有的 .xlsx、.xlsm、.xltx、.csv、.tsv 文件(加列、算公式、调格式、画图表、清洗脏数据);从零或从其他数据来源新建表格;在表格格式之间转换。用户提到某个表格文件名时尤其要用——哪怕说得很随意(比如"我传的那个 Excel")——只要他要对它做点什么或从它产出东西。把格式混乱的表格数据(错行、表头错位、垃圾数据)整理成规范表格也用它。交付物必须是表格文件。主要交付物是 Word 文档、HTML 报告、单独的 Python 脚本、数据库流程或在线表格接口时,即使涉及表格数据也不要用。
---
# XLSX:生成、修改与分析
| 任务 | 做法 |
|---|---|
| 带公式/格式地**生成**或**修改** | `openpyxl`——坑点见下 |
| **大批量**数据读写 | `pandas`(`read_excel`、`to_excel`) |
| **快速看一眼**某张表 | `markitdown 文件.xlsx`——每张表一个 `## 表名`,也能读 `.xlsm`。没有单元格坐标,不要据此规划修改 |
| **读懂**一个模型(公式**和**值) | `load_workbook` 读两遍——见坑点 |
> `openpyxl`、`pandas`、`markitdown` 都已装好,直接 import。沙箱没有网络,不要 `pip install`。
> 脚本在 `/opt/knowwork/office/`。用户给的原文件在 `/workspace/input`(只读),中间文件放
> `/workspace/tmp`,要交给用户的放 `/workspace/output`,写好后调用 `present` 交付(例如 `present({"files":[{"path":"/workspace/output/表格.xlsx"}]})`)。
## 每份输出的要求
- **全表用正规字体**(Arial、Times New Roman;中文内容用 等线 或 微软雅黑),用户另有要求除外。
- **零公式错误。** `recalc.py` 报 `errors_found` 时绝不交付。你认为某个错误是原来就有的,就证明它:
用 `data_only=True` 打开**原文件**看那个格子。你引入的错误和继承来的错误看起来一模一样。
- **用公式,不要写死结果。** 写 `sheet['B10'] = '=SUM(B2:B9)'`,而不是 Python 算好的合计。输入变了,
表格要能自己重算。
- **严格按用户的要求做。** 表名、列标题、用户写出来的公式都一字不差。算的是别的东西的"重新设计",
再漂亮也是失败。
- **每个假设和写死的数字都要写明**,写在读的人看得到的地方——单元格批注,或表格末尾相邻的格子。
有真实出处就注明(`来源:公司 2024 年年报,第 45 页,营业收入附注`);数字是用户给的,就直说。
- **你新建、要别人填写的表格**,要有一段简短的图例说明哪些格子可以改,并给一行真实的示例数据示范格式。
要求你**修改**的文件里绝不要加这样的示例行。
- **修改已有文件:完全遵循它的惯例。** 它的惯例优先于这里的所有规则。先找出它指定的输入格——
特别的字体颜色、填充或底纹通常就是标记——只往那里写,已有的公式一个都不动。
## 重算(只要文件里有公式就必做)
openpyxl 把公式当字符串写进去,**没有缓存值**。重算之前,任何读缓存值的东西——`pandas`、
`load_workbook(data_only=True)`、大多数预览——读出来的公式格都是 `None`。
```bash
python3 /opt/knowwork/office/recalc.py /workspace/output/文件.xlsx [超时秒数] # 默认 60
```
LibreOffice 会算出每个公式,文件**原地重写**,并输出 JSON:`status`(`success` | `errors_found`)、
`total_formulas`、`total_errors`,以及 `error_summary`——每种错误最多列 100 个格子(`locations_truncated`
说明还有多少没列——以 `total_errors` 为准,不要数列表长度)。修掉它列出的问题再跑一次。**JSON 里是
`error` 而不是 `status`,说明什么都没算**,也只有这种情况退出码非 0——`errors_found` 的退出码是 0,
所以绝不要把退出码为 0 当成表格没问题。
**重算通过只证明公式能*算出值*,不证明它们*算得对*。** 范围差一行、引用错了行,得到的是一份干干净净、
没有错误、数字却错了的文件。先写 2–3 个公式,确认它们取到了你预期的值,再铺满整片区域。
**链接了其他文件的工作簿**,用 openpyxl 重存再重算会丢掉那些链接。这类公式长这样:
`='[1]回报分析'!$B$2`——`[1]` 是工作簿外部引用列表里的序号,指向**磁盘上另一个文件**,不是某张表。
那个文件在这里几乎不会有,所以格子里的缓存值是唯一保存着它数据的地方。openpyxl 保存时会清掉这个值;
LibreOffice 于是只能真的去解析引用,失败后写入 `#NAME?` 并删掉所有链接。这种情况下 `recalc.py`
会拒绝运行——先把那些格子的值从原文件里抄出来,再覆盖保存(`--force` 可以强制运行,并接受链接丢失)。
## 选能通过校验的公式
LibreOffice 支持的函数比 Excel 少,它算不了的函数会变成一个实打实的 `#NAME?` 写进你交付的文件。
- **优先用 Excel 2007 时代的函数**——`SUMIFS`、`INDEX`、`MATCH`、`IFERROR`、`SUMPRODUCT`——不需要前缀。
- **六个 2007 年以后的函数可以用,但必须加 `_xlfn.` 前缀**,因为 openpyxl 会把公式原样写进 XML,
而 Excel 存储这些新函数名时带前缀(界面里看不到):`_xlfn.TEXTJOIN`、`_xlfn.CONCAT`、`_xlfn.IFS`、
`_xlfn.SWITCH`、`_xlfn.MAXIFS`、`_xlfn.MINIFS`。不加前缀就是 `#NAME?`。
- **绝不要用 `XLOOKUP`、`XMATCH`、`SORT`、`FILTER`、`UNIQUE`、`SEQUENCE`。** 这里的 LibreOffice 是
较新的版本,能算出其中一些,但它们是溢出型数组函数,openpyxl 写出的文件没有溢出元数据,所以只有区域
左上角那一格有值——而 `recalc.py` 对这个被截断的结果照样报 `total_errors: 0`。另外用户若用较老的
Excel,这些函数本身就打不开。查找用 `INDEX`/`MATCH`,排序、筛选、去重在 Python 里做完再写进格子。
- LibreOffice 解析不了的公式会被**改成小写**写回去——看到 `#NAME?` 旁边的公式是小写,就是这个原因。
## openpyxl 坑点
- **读懂一个模型要读两遍。** `data_only=True` 拿到缓存值,公式没了;默认方式拿到公式字符串,没有值。
一遍拿不到两样。
- **用 `data_only=True` 打开后保存是破坏性的。** 那个工作簿里已经没有公式了,保存就把每一个公式都
永久替换成了固定值。
- **对 openpyxl 刚写出的文件用 `data_only=True`,读到的全是 `None`**——先跑 `recalc.py`。
(结果为 `""` 的公式读回来也是 `None`。)
- **合并单元格:只写左上角那一格。** 区域里其他格子都是 `MergedCell`,`.value` 只读。
- **`.xlsm` 不传 `keep_vba=True` 给 `load_workbook` 就会丢掉宏。**
- **表名里有空格的,跨表引用时必须加引号**:`='假设 输入'!$B$5`。不加引号算出来是 `#VALUE!`。
- **openpyxl 读不进图表、图片、形状和数据透视表,存回去它们就没了。** 改已有文件前先看压缩包里有没有
`xl/charts/`、`xl/drawings/`、`xl/pivotTables/`(`unzip -l 文件.xlsx`);有的话不要整本重存覆盖,
另存一份并在回答里说明哪些内容没带过去。
## 财务模型
用户没另说、已有文件也没有别的做法时,按下面来。
**颜色:** 写死的输入和情景变量用蓝字(`0,0,255`)· 公式用黑字 · 引用其他表用绿字(`0,128,0`)·
引用其他文件用红字(`255,0,0`)· 关键假设和需要用户填写的格子用黄色填充(`255,255,0`)。
**数字:** 货币 `$#,##0`(人民币 `¥#,##0`),单位写在表头里(`营业收入(百万元)`)· 零显示为 `-`,
百分比也一样(`$#,##0;($#,##0);-`)· 负数用括号 · 百分比 `0.0%`,**存成小数**(`0.15` 显示为 `15.0%`;
存 `15` 就显示成 `1500.0%`)· 估值倍数 `0.0x` · 年份存成文本(`"2024"`,绝不要显示成 `2,024`)。
**结构:** 每个假设放在自己的带标签的格子里,公式去引用它(`=B5*(1+$B$6)`,绝不写 `=B5*1.05`)·
每个预测期的公式保持一致——一行中间某个格子被单独改过,是最常见的隐蔽错误 · 可能为零的分母要做保护。
## 回答里说什么
说清楚表格里有什么、哪些格子是公式、重算结果(有没有错误)、做了哪些假设。不要写 `/workspace` 路径,
文件会作为附件随回答交给用户。
## 依赖
`openpyxl`、`pandas`、`markitdown`(Python,已装)· LibreOffice(通过 `/opt/knowwork/office/soffice.py`
与 `recalc.py` 调用)