# Excel 表格自动化技能包

<!-- autos.skill.namespace: autos.skills.excel -->
<!-- autos.skill.description: Excel 保存值/结构/筛选汇总优先原生 fsys.openXml.xlsx；精确显示、重算、写入及旧格式走 Office/WPS COM；规整二维表可走 access SQL helper -->
<!-- autos.skill.version: 0.6.0 -->
<!-- autos.skill.minAutos: 4.13 -->

## 定位与边界

目标是低成本读懂表格、查询数据，并在需要真实应用语义时使用 Excel/WPS。

1. **XLSX 保存值、结构、常规查询：优先原生** `inspectXlsx/readXlsxSheet/queryXlsx/findXlsxCells`。不依赖 Office/ACE，不写回源文件；底层实现只在 fsys.openXml，不在技能里复制。
2. **普通 XLSX 数据生成：有限原生** `writeXlsx()`。适合新建多表数据、显式公式和调用者缓存，不计算公式、不编辑既有文件。
3. **精确显示、公式重算、复杂写入/复杂操作及 XLS/XLSB/加密：COM**。使用 `com.excel` 对象模型，不能把保存缓存当成最新计算结果。
4. **适合 SQL 的规整二维表：access SQL helper**。Jet/ACE 有混合列类型推断局限；技能不自动安装驱动。
5. 旧 `readSheet/readWorkbook(method="auto")` 仍按 COM→ZIP→SQL 选路；本次没有改变其兼容行为。不封装另一套第三方 Excel 引擎。

## 先选路线

| 需求 | 首选路线 |
|---|---|
| XLSX 保存值、结构、有界预览 | `inspectXlsx()` / `readXlsxSheet(...,{range=...})` |
| XLSX 常规筛选/排序/查找/汇总 | `queryXlsx()` / `findXlsxCells()`，结构化条件 |
| 沿用原有一站式读取 | `readSheet()` / `readWorkbook()`，默认 `method="auto"` |
| 已安装 Excel/WPS，需最大兼容 `.xls/.xlsx/.xlsm/.xlsb` | COM：`openWorkbook()`、`sheetToTable()`、`readSheet(...,{method="com"})` |
| 无 Office，只读基础 `.xlsx/.xlsm` 文本和单元格值 | ZIP：`readSheet(...,{method="zip"})`、`readWorkbookByZip()` |
| SQL 查询规整表格 | `readSheetBySql()`、`queryExcel()`、`openExcelDb()` |
| 生成普通 `.xlsx` 数据工作簿，不要求公式重算 | 原生：`writeXlsx()` |
| 写入/生成 Excel，要求精确显示、重算或复杂操作 | COM：`tableToSheet()`、`saveAs()` |
| 既有普通 `.xlsx` 固定区域填值/清除，不要求原生重算 | 原生：`openXlsxEditor()` |

## 可用入口

```aardio
import autos.skills.excel;
var xl = autos.skills.excel;
```

关键说明：

- `xl.getApp()` 返回标准库 `com.excel` 包装对象，底层兼容 `Excel.Application`、`Ket.Application`、`ET.Application`。
- `com.excel` 包装对象可继续按 Excel VBA COM 对象模型操作，例如 `excel.WorkBooks.Add()`、`sheet.Cells(1,1).Value2`。
- `readSheet/readWorkbook` 默认 `method="auto"`：优先创建隐藏 Excel/WPS COM 实例读取；失败后 `.xlsx/.xlsm` 尝试 ZIP 基础解析；最后可尝试 SQL helper。
- ZIP/Open XML 只读函数仅做低成本基础解析，不负责完整样式/公式/图表/透视表。
- `writeXlsx()` 是新增的有限原生生成入口；只生成普通 `.xlsx` 数据工作簿，不修改既有文件，不依赖 Office/ACE。

## 原生 XLSX 数据生成

`writeXlsx(path,document,options)` 是 `fsys.openXml.xlsx.builder.write` 的技能薄转发。它适合生成普通数据工作簿：多工作表、二维数组或显式单元格、字符串/数值/布尔值、`time` 日期时间、稀疏位置、普通公式及调用者提供的缓存，也支持有限的版式元数据和 Worksheet `autoFilter` 区域声明。

```aardio
import autos.skills.excel;
var xl = autos.skills.excel;

var output,err,report = xl.writeXlsx("/report.xlsx",{
    date1904 = false;
    sheets = [
        {name="数据";data=[
            ["商品","数量","备注"];
            ["苹果",3,"=这仍然是文本"];
            ["长编号","001234567890123456789",null];
        ]};
        {name="计算";cells=[
            {ref="A1";value="输入"};
            {ref="B1";formula="SUM(1,2)";cached=3};
            {ref="C1";formula="NOW()"};
            {ref="D1";value=time("2024-02-29T12:00:00")};
        ]}
    ];
},{creator="aardio";title="示例报表"});
assert(output,err);
```

约定：

- `null` 不写入单元格；空字符串、`0`、`false` 都是有效保存值。普通字符串（包括以 `=` 开头的字符串）永远按文本写入；长编号应以文本传入。
- 公式必须显式使用 `formula`。`cached` 是调用者提供的保存缓存；省略缓存不会写入伪造的 `0`，生成器只设置打开时重算请求，不计算公式。
- 日期使用 `time` 对象或 `kind="date"/"datetime"` 的无时区 ISO 文本，原生转换为 Excel 数值并使用内建日期格式；`date1904` 会写入工作簿日期系统。
- `buildParts()` 只构造并校验部件，不写文件；`write()` 默认不覆盖，使用同目录临时 ZIP 原子提交，并可用 `protectPaths` 防止覆盖源文件。
- 该入口不提供完整样式/显示引擎、表对象、图表、宏、外链或公式计算。当前只提供低风险报表样式子集；需要公式重算、最新显示文本、复杂格式或 `.xls/.xlsb/加密文件` 时改用 COM。

### 原生报表样式子集

`document.styles` 定义命名样式，单元格描述用 `style="样式名"` 引用；同一工作簿内相同字体、填充、边框、数字格式和对齐会去重。示例：

```aardio
var output,err,report = xl.writeXlsx("/report.xlsx",{
    styles=[
        {name="title";font={size=16;bold=true;color="FFFFFF"};fill="1F4E78";alignment={horizontal="center"}},
        {name="header";font={bold=true;color="FFFFFF"};fill="4472C4";border={style="thin";color="D9E2F3"};alignment={horizontal="center";wrapText=true}},
        {name="money";numberFormat="#,##0.000"}
    ];
    sheets=[{name="数据";data=[
        [{value="标题";style="title"},null,null];
        [{value="金额";style="header"},{value=1234.5;style="money"}]
    ];columns=[{min=1;max=3;width=18}];rows=[{row=1;height=28}];merges=["A1:C1"];freeze={rows=2;columns=1}}]
},{creator="aardio"});
```

支持的版式字段：

- `font`：`name/size/bold/italic/strike/underline/color`；颜色为 6 或 8 位十六进制值，也可传 32 位颜色数值。
- `fill`：颜色快捷写法或 `{color;patternType="solid"}`，只支持 `none/solid`。
- `border`：`{style;color}` 可同时设置四边，也可分别设置 `left/right/top/bottom`。
- `alignment`：`horizontal/vertical/wrapText/shrinkToFit/textRotation/indent/readingOrder`。
- `numberFormat`：内建格式 ID 或格式字符串；内建格式复用，其他格式去重后写入自定义 `numFmt`。
- `columns` 的 `min/max/width/hidden`、`rows` 的 `row/height/hidden`、矩形 `merges`、`freeze={rows;columns}` 及 `autoFilter="A1:E100"`。

这是保存层的有限样式与结构元数据，不保证 Excel/WPS 的最终显示、主题继承、条件格式或精确 AutoFit；复杂报表继续使用 COM。`autoFilter` 只声明筛选区域，不保存筛选条件，也不创建 Excel 表对象；需要自动筛选、排序或完整表对象语义时使用 COM。

## 原生 XLSX 受控 editor

`openXlsxEditor(path,options)` 是 `fsys.openXml.xlsx.editor.open` 的技能薄转发，用于既有普通 `.xlsx` 的固定坐标内容编辑。它不启动 Office/WPS，不依赖 .NET；源文件以只读会话打开，必须另存为新的 `.xlsx`。

```aardio
import autos.skills.excel;
var xl = autos.skills.excel;
var ed,err = xl.openXlsxEditor("/template.xlsx");
assert(ed,err);

var cell,ce = ed.getCell("数据","B3");
var count,we,report = ed.setCell("数据","B3",{value="已完成"});
assert(count,we);

// 严格二维矩形；null 跳过，空串/0/false 都是有效值。
count,we,report = ed.setRange("数据","C3:E3",[[null,0,false]]);
assert(count,we);

var output,se = ed.save("/filled.xlsx",{overwrite=true});
assert(output,se);
ed.close();
```

支持的会话方法：

- `listSheets()`：列出工作表；只允许普通 `worksheet` 作为单元格目标。
- `getCell(sheet,ref)` / `getRange(sheet,ref,options)`：读取会话内视图；公式 `value` 仅是保存缓存，无缓存为 `null`，不返回伪造的 `0`。`getRange` 默认返回稀疏 `cells`，`includeRows=true` 才展开 `values/rows`。
- `setCell(sheet,ref,{value=v|formula=f|clear=true},options)`：描述必须三选一；普通值覆盖已有公式需 `allowFormulaOverwrite=true`。合并区域只允许左上角主格。
- `setRange(sheet,ref,values,options)`：输入必须是与 A1 矩形完全一致的二维数组；`null` 跳过；任一位置失败整批不提交。
- `save(outputPath,options)` / `close()`：默认不覆盖，拒绝源文件和可识别硬链接别名；`close` 丢弃未保存修改。

发生真实内容变化时，editor 保留公式 `<f>`，清除全工作簿公式缓存，删除 `calcChain` 部件/关系/内容类型声明，并设置 `calcPr` 的自动全量重算请求。它不计算公式，也不保证图表、透视缓存、表对象计算列或外链结果同步；需要文件立即包含最新结果时使用 Excel/WPS COM 重算并保存。

首版拒绝宏启用/旧格式/加密/签名包、工作簿保护、受保护目标工作表、共享/数组/数据表公式目标、表对象结构目标、插入删除行列和样式编辑。日期可用 `kind="date"/"datetime"` 的无时区 ISO 文本，但不推断或新建样式。失败返回 `null,err`，不会提交本次写操作。

## 最常用：快速读懂一个 Excel

```aardio
import autos.skills.excel;
var xl = autos.skills.excel;

// 读第一个工作表，默认首行作为字段名；自动跳过全空行。
var rows,err = xl.readSheet("/data.xlsx",1,{
    maxRows = 200;          // 限制读取行数，适合先看结构
    maxColumns = 30;        // 限制列数
    includeRowNumber = true;// 每行增加 _row，记录 Excel 原始行号
});
assert(rows,err);

// rows 是表数组；rows.fields 是字段名数组。
for i,row in rows{
    // 字段名会自动 trim、空标题补 ColumnN、重名补 _2/_3
    print(row._row,row[rows.fields[1]]);
}
```

读取整个工作簿：

```aardio
var book,err = xl.readWorkbook("/data.xlsx",{
    maxSheets = 3;
    maxRows = 200;
    maxColumns = 30;
});
assert(book,err);

for i,sheet in book.sheets{
    print(sheet.name,sheet.method,sheet.rowCount,sheet.fields);
}
```

返回结构概要：

- `readSheet()` 返回 `rows` 表数组，附加 `rows.fields`、`rows.info`。
- `readWorkbook()` 返回 `{path, method, sheetCount, sheets}`。
- `book.sheets[i].rows` 是该表的表数组；`book.sheets[i].fields` 是字段名数组。

## 数据处理增强选项

`sheetToTable()`、`rowsToTable()`、`readSheet()`、`readWorkbook()` 支持常用选项：

```aardio
{
    header = true;              // 默认 true，首行作为字段名；false 返回二维数组或按 fields 映射
    fields = ["姓名","分数"];   // 自定义字段名
    maxRows = 200;
    maxColumns = 30;
    row = 1; column = 1;        // COM 模式下指定起始单元格
    keepEmptyRows = false;      // 默认跳过全空行
    stopAtBlankRow = false;     // 遇到首个空行即停止
    trim = true;                // 默认 trim 字符串值
    trimFieldNames = true;      // 默认 trim 字段名
    dedupeFields = true;        // 默认重名字段补 _2/_3
    emptyStringAsNull = false;  // 空字符串是否转 null
    includeRowNumber = true;    // 增加 _row 字段
    valueMode = "auto";         // auto/value/text；auto 优先 Value2，并识别日期格式转 time 对象
}
```

常用函数：

- `sheetToTable(sheet, options)`：COM 工作表转表数组。
- `tableToSheet(sheet, data, options)`：二维数组或表数组写入工作表。
- `rowsToTable(rows, options)`：把二维数组整理成表数组，供 ZIP/Open XML 结果或其他来源复用。
- `readSheet(path, sheet=1, options)`：一站式读取单个工作表。
- `readWorkbook(path, options)`：一站式读取工作簿多个工作表。

## COM 写入最小流程

```aardio
import autos.skills.excel;
var xl = autos.skills.excel;

var book,err = xl.createWorkbook(true,false);
assert(book,err);

var sheet = book.Sheets(1);
sheet.Name = "数据";

xl.tableToSheet(sheet,[
    {姓名="张三";分数=98},
    {姓名="李四";分数=87},
],{
    fields = ["姓名","分数"];
    header = true;
});

var path,err = xl.saveAs(book,"/output.xlsx");
xl.closeWorkbook(book,false);
xl.quitApp();
```

## access SQL helper

适合规整二维表，不适合保留格式。

```aardio
import autos.skills.excel;
var xl = autos.skills.excel;

// 查询 Sheet1，表名会自动变为 [Sheet1$]。
var rows,err = xl.readSheetBySql("/data.xlsx","Sheet1",{
    top = 100;
    where = "分数 >= 90";
    orderBy = "分数 DESC";
});

// 自定义 SQL。
var rows,err = xl.queryExcel("/data.xlsx",
    "SELECT TOP 20 * FROM [Sheet1$] WHERE 分数 >= 90"
);
```

SQL helper 函数：

- `hasAceOleDb(legacy12=true)`：检测 ACE OLEDB 16/12 驱动。
- `openExcelDb(path,{header=true})`：打开 Excel OLEDB 连接。
- `listExcelSqlTables(path)`：列出可查询表名。
- `sqlSheetName("Sheet1")`：返回 `[Sheet1$]`。
- `queryExcel(path, sql, params, options)`：执行 SQL 并返回表数组。
- `readSheetBySql(path, sheet, options)`：拼出常用 SELECT。

注意：

- `.xls` 可用系统 Jet；`.xlsx/.xlsm/.xlsb` 通常需要 ACE OLEDB 16/12。
- `access.oleDb` 可安装驱动，但安装需要权限，技能包默认不自动下载/安装。
- OLEDB 对混合类型列有类型推断问题，可能丢值或截断；复杂数据优先 COM。

## 基于 fsys.openXml 的 ZIP 读取

需要 `fsys.openXml` **0.5.0+**。技能包不再自行解包或用固定前缀正则解析 XML；七个旧 OpenXML 入口及 `readSheet` 的 ZIP 分支统一转发 `fsys.openXml.xlsx`。COM、SQL、auto 优先级不变，COM 路径不加载 ZIP 读取器。

保留旧的标题整理、trim、空行、行号等选项：

```aardio
import autos.skills.excel;
var xl = autos.skills.excel;
var rows,err = xl.readSheet("/data.xlsx",1,{
    method="zip";maxRows=200;maxColumns=30;includeRowNumber=true;
});
if(!rows) return null,err;
return rows;
```

新任务需要公式/缓存等原始信息时，直接使用基础库：

```aardio
import fsys.openXml.xlsx;
var data,err = fsys.openXml.xlsx.readSheet("/data.xlsx",1,{
    maxRows=200;maxColumns=30;
});
if(!data) return null,err;
for i,cell in data.cells {
    print(cell.ref,cell.value,cell.formula,cell.hasCachedValue);
}
return data;
```

底层入口：

- `listXlsxEntries(path,pattern,options)`：包条目清单，保留目录。
- `listXlsxSheets(path,options)`：按真实工作簿顺序列页，含隐藏页。
- `readXlsxSheet(path,sheet,options)`：保留旧二维数组/单元格字段，增加公式、缓存、日期基准、警告与截取信息。
- `readWorkbookByZip(path,options)`：一次开包读取多表，不再每页重开共享字符串表。
- `extractTextFromXlsx(path,options)`：按明确行列宽度保留稀疏位置。
- `listXlsxMedia(path,options)`、`extractXlsxMedia(path,outDir,overwrite=true,options)`：共享媒体导出实现。

原生媒体导出默认不覆盖：

```aardio
import fsys.openXml.xlsx;
var result,err,partial = fsys.openXml.xlsx.extractMedia("/data.xlsx","/media");
if(!result) return null,err,partial;
return result;
```

### 兼容与正确性

- 原生支持 XLSX/XLSM/XLTX/XLTM/XLAM 的 XML 工作表；XLS/XLSB/加密文档不解析。类型按包结构识别，不只看后缀。`::Kernel32.GetACP()===65001)`时注意 VBA 本就不支持 UTF-8 打开 XLSM 会报 Open 方法无效。
- 原生 `maxRows/maxColumns/maxSheets/maxMedia=0` 表示不取；技能兼容层的这四个 0 仍表示未指定，并兼容数字字符串。包/XML 安全限额照常生效。
- `asMatrix=true` 现在也遵守行列限额，`rows.info` 包含 `warnings/truncated`。纯数据 `rowsToTable` 修复 0/false 被伪三元丢失、空字符串转换吞数值以及稀疏列宽问题；COM 的实现不变。
- 原生返回区域内空格为 null，0、false、有效空串分别保留。行的普通 `length` 字段记录完整列宽；用 `columnCount` 或 `table.len(row)` 遍历，不用 `#row` 推断稀疏行宽。
- 工作表坐标自 1 开始，整簿 `_row` 现在包含正确起始行偏移。共享字符串/富文本和实体由命名空间感知的 XML 处理；不把注音混入正文。
- 公式原文、保存缓存与是否有缓存分开。**不重新计算**，无缓存不是 0，缓存可能过期。原生 `parseDates=true` 仅给 cell 附加 interpretation，不改变原始数值/rows；COM 的 valueMode/日期返回语义不套用到原生路线。完整显示格式仍走 COM。
- 清单可列图表页等类型，但不解析其单元格。整簿坏页默认失败，不再静默吞掉；`keepErrors=true` 才继续并保留错误项，查看 `errorCount/complete`。
- 原生矩形展开默认累计最多 1000000 格，`extractText` 默认最多 16 MiB 文本。可限制区域，原生 `includeRows=false` 只取稀疏 cells；它不是流式读取，输出限额不阻止工作表 DOM 解析。
- 旧媒体导出默认覆盖；显式 false 或 0 不覆盖。成功仍为文件数组，第三值为原生明细；失败 `null,err,partial`。子目录媒体统一平铺；重名预检，CRC/限额验证后逐文件提交，既有成功项不回滚。
- 不执行宏、外链或嵌入对象；不写回工作簿，不解释完整样式/图表/透视表/MC 视图。

## 原生结构与查询（fsys.openXml 0.15.0+）

新入口直接转发基础库，**不使用旧兼容层的0=未指定或数字字符串转换**：

- `inspectXlsx(path,options)`：默认20表/20行/20列预览，含结构、保存样式、日期解释、全页存储格统计和表头候选。结构元信息仍是完整页的，不受预览行列限制。
- `readXlsxSheet` 可增加 `range="B3:M8"`、`includeStructure=true`、`includeStyles=true`、`parseDates=true`。原始值不变，日期解释位于 cell.interpretation；`actualRange` 与声明 dimension 分开；合并格不补主值；隐藏行列不自动删除。
- 名称定义保留表达式和作用域；只解析单个静态区域，不求值动态公式或访问外部工作簿。需要基于名称查询时用其 targetSheetIndex/range.ref 显式定位。
- `queryXlsx(path,sheet,options)`：range选区，header默认true且只取选区第一行；columns投影、where的all/any组合、orderBy、offset/limit、count/sum/min/max/average。无SQL/eval/回调，不做GROUP BY/JOIN。
- `findXlsxCells(path,sheet,options)`：target=value/formula，按字面值或保存公式正文匹配；只扫描已存储单元格，不枚举缺失格。

```aardio
import autos.skills.excel;
var xl=autos.skills.excel;
var result,err=xl.queryXlsx("/data.xlsx",1,{
    range="B3:M8";
    columns=["Name","Number"];
    where={column="Number";op="ge";value=0};
    orderBy=[{column="Number";descending=true}];
    maxScanRows=10000;maxScanCells=200000;
    limit=20;
    aggregates=[{name="total";op="sum";column="Number"}];
});
if(!result) return null,err;
return result;
```

查询关键规则：

- 条件叶子 `{column,op,value}`；op为eq/ne/gt/ge/lt/le/contains/in/isNull/isEmpty，组合 `{all=[...]}` 或 `{any=[...]}`。最多256节点/16层，in最多10000值。大小比较仅同类型数字/字符串；null、空串、0、false、数字文本不混同。
- column字符串是整理后的标题；数值是**绝对列号**。投影可用 `{column=...,name="别名"}`。结果rows项含原行号row、稀疏values、refs；values用table.len而非#遍历。
- 扫描默认maxScanRows=100000、maxScanCells=1000000；limit默认100。扫描计选区数据坐标行（不含标题，含空行成本），limit只限制返回结果。0扫描与0结果严格区分；查询拒绝maxRows/maxColumns。
- 聚合先于分页，仅针对扫描到的匹配行；报告时必须检查 `scanTruncated/scanComplete`、scannedRows/matchedRows、scannedRange，不得把局部统计说成全表。数值汇总不转换数字文本、布尔、错误或无缓存值。
- `visibleOnly=true` 只反映保存的隐藏状态，不重新执行筛选。默认投影排除隐藏列，显式访问隐藏列时报错。
- 日期识别保守，1900序号60标记虚构闰日；时间与累计时长区分，百分比不乘100；复杂格式保留原文，不模拟显示引擎。
- 仍基于DOM，扫描/输出限额不阻止整张XML解析；通用包/XML安全限额照常执行。不宣称百万行流式能力。

完整接口与示例见 [XLSX 原生查询](https://www.aardio.com/zh-cn/docs/library/fsys/openXml/xlsx/query.html)、[结构与日期](https://www.aardio.com/zh-cn/docs/library/fsys/openXml/xlsx/structure.html)。

## 默认工作流

1. XML工作簿先 `inspectXlsx` 看实际区域、表对象/名称、预览与表头候选；明确真正的数据区域，不把候选自动认定为标题。
2. 原始单元格与日期/样式用 `readXlsxSheet`；常规筛选/汇总/查找用 `queryXlsx/findXlsxCells`，检查扫描完整性。
3. 生成普通新工作簿用 `writeXlsx`；写入后用原生读回或 SDK 验证，公式缓存仍视为调用者提供的数据。
4. 需要精确显示或最新公式结果时使用 COM；不承诺原生缓存最新。XLS/XLSB/加密继续 COM。
5. 既有普通 `.xlsx` 固定区域内容编辑可用 `openXlsxEditor`；复杂结构、表对象、重算与精确显示继续 COM。
6. 适合 SQL 且已有驱动可用 SQL helper；写入/生成仍用 `tableToSheet + saveAs`。
