# fsys.openXml：Office Open XML 文件处理基础库

`fsys.openXml` 以**读取与分析优先，按需扩展受控编辑与生成**为定位。当前提供共享 ZIP 部件、内容类型、关系及媒体导出；`fsys.openXml.pptx/docx/xlsx` 提供对应格式的高层读取入口。

**0.7.0 增加 PPTX 渐变与透明度、图片等比适配/裁切、段落间距和样式表格。** 完整 6 页示例见 `~/examples/File/OpenXML/pptx-design.aardio`。

**0.8.0 增加生成前版式预检 `builder.check`**（文字溢出、表格被撑高、超出页面、文字重叠，不需要 Office），并支持中英文分设字体、语言标签、备注独立字号、虚线与箭头、两点画线；同一图片只嵌入一份，生成错误带页号与形状名。详见 [PPTX 生成与预检](pptx/builder/index.html)。

**0.6.0 新增 PPTX 生成、常用动画与有限编辑另存。** 保持原有 open/read 只读；不以完整 Office 编辑器、排版渲染引擎或 OPC/XSD 校验器为目标。生成与编辑见 [PPTX 生成](pptx/builder/index.html)、[PPTX 有限编辑](pptx/editor/index.html)。

0.2.0 起 XML 后端使用 `web.msxml`（Windows 系统 MSXML COM 组件），ZIP 读取复用 zlib；不需要 Office、WPS、.NET 或其他语言运行时。**0.5.0 新增 XLSX 基础读取与 Excel 技能兼容接入，PPTX/DOCX/XLSX 共用媒体导出。** 读取不创建临时解压目录；仅显式导出才写入目标文件，不修改源包。

0.5.1 起 ZIP 层完全由标准库 `zlib.unzip` 对象提供：`zlib.unzip(path,false)` 只读模式打开、`eachFile/getPos/setPos` 建立随机访问索引、`readCurrentFile` 整读并校验长度与 CRC。本库不再声明或调用任何 zlib 底层 API，ZIP 相关能力如需增强应在 `zlib.unzip` 中补充，而不是在这里重复封装。

**0.9.0** 支持逐段落动画、Morph 与兼容淡出回退、标题占位符/标准版式。**0.10.0** 新增页码字段、点击超链接、原生编号列表、外阴影与可控圆角；详见 [页码与交互细节](pptx/builder/details.html)，演示见 `~/examples/File/OpenXML/pptx-details.aardio`。这些新增能力仍优先由原生生成器完成。

**0.11.0** 新增同文稿复制/删除幻灯片、保留格式的表格填值与矩形合并，详见 [模板与数据](pptx/editor/data.html)。复制已有常见图表时，图表与内嵌工作簿独立克隆；用户母版导入与近似预览暂未实现。

**0.12.0** 新增二维柱状/条形/折线/饼图的小范围原生生成，每图有独立内嵌XLSX，分类、系列、数值引用与缓存一致，0/null严格区分。见 [二维原生图表](pptx/chart/index.html) 与 `~/examples/File/OpenXML/pptx-charts.aardio`。不等于已支持任意图表编辑；XLSX打包能力仅供嵌入数据使用，不是完整Excel写入引擎。

**0.12.1** 修复 WPS 导入表格时边距归零及垂直对齐被覆盖的问题：表格单元格的边距与对齐只写入 `tcPr`，内部 `bodyPr` 不再重复声明。普通文字、备注与预检计算不变；已有旧文件不会被自动迁移，重新运行生成示例即可得到修正版。详见 [PPTX 生成](pptx/builder/index.html)。

**Word 结构化生成**：`fsys.openXml.docx.builder` 生成段落/标题、格式化运行、普通及嵌套表格、编号、书签和超链接，使用公共 writer 完成写包。`string.html.toDocx` 只保留 Markdown 所需 HTML 语义适配，复用该生成器。见 [Word 生成指南](docx/builder/index.html) 与 `~/examples/File/OpenXML/docx-write.aardio`；这是新建 DOCX 的有限子集，不是既有 Word 文档编辑器。

## 模块与依赖

- `fsys.openXml`：通用包读取入口，导入时加载 `zlib.unzip`（需要 aardio 自带的、支持只读模式与 `readCurrentFile` 的 `zlib.unzip` 版本）。
- `fsys.openXml.xml`：独立 XML 辅助，不加载包读取入口或 `zlib.unzip`。
- `fsys.openXml.pptx`：PPTX 内容、备注、清单与媒体读取；write/edit 为便捷转发，默认读取输出不变。
- `fsys.openXml.pptx.builder`：独立纯生成，文本/形状/PNG与JPEG/表格/二维图表/备注/常用动画与切换，不加载读取器。
- `fsys.openXml.pptx.editor`：有限编辑与另存，组合包 editor 与动画 helper。
- `fsys.openXml.writer/editor`：共享 ZIP 写入与现有部件替换；`uri/file` 为 reader-free 共用小工具；ZIP 能力全部在 zlib.zip/unzip 公共库。
- `fsys.openXml.docx`：DOCX 段落、表格、关系驱动的附属文本、属性及媒体读取；write 便捷转发生成器，读取入口不加载生成器。
- `fsys.openXml.docx.builder`：Word 结构化纯生成，不加载 HTML、读取器、MSXML 或 Office。
- `fsys.openXml.xlsx`：XLSX 保存值/公式缓存、A1区域、结构/样式与日期解释、有界预览及媒体读取，见 [XLSX 读取](xlsx/index.html)；0.15.0 增加 [原生结构化查询](xlsx/query.html)（筛选/投影/排序/查找/基础汇总，无 Office/ACE），并提供有限的 [原生数据生成](xlsx/builder.html)。不重算公式或模拟完整数字显示格式。
- `fsys.openXml.media`：三种格式共用的目录枚举与平铺导出，接收打开的包，不负责高层识别或生命周期。
- `autos.skills.powerPoint/word/excel`：旧 OpenXML 入口只转发并适配选项/字段；Office/WPS COM 功能独立，调用 ZIP 读取入口时才导入对应子库。基础库不反向调用技能包。

后续编辑与生成按需组合基础模块和 ZIP 读写模块，不要求纯生成器为了复用公共代码而加载 ZIP 读取器。现有 open/read 系列接口保持不修改源件；新增写入使用明确的写入或另存接口。

## 读取 PPTX

```aardio
import fsys.openXml.pptx;
var data,err = fsys.openXml.pptx.read("/input.pptx");
if(!data) return null,err;

for i,text in data.slides {
    print("第 " + i + " 页",text);
}
return data;
```

页面顺序由主文档确定，不按 ZIP 枚举顺序或文件编号排序。详见 [PPTX 文本读取](pptx/index.html)。

## 读取 DOCX

```aardio
import fsys.openXml.docx;
var data,err = fsys.openXml.docx.read("/input.docx");
if(!data) return null,err;
print(data.text);
return data;
```

保留段落/单元格边界，按真实关系定位主文档及请求的附属部件，不依赖固定 `w:` 前缀。输出是普通数据，不是页面渲染；内容截取通过 `truncated/warnings` 报告。详见 [DOCX 内容读取与媒体](docx/index.html)。

## 读取包内文件

```aardio
import fsys.openXml;
var pkg,err = fsys.openXml.open("/input.pptx");
if(!pkg) return null,err;

var mainPart,err = pkg.getMainPart();
if(!mainPart){ pkg.close(); return null,err; }

var root,err = pkg.readXml(mainPart);
var parts = pkg.listParts();
pkg.close();
if(!root) return null,err;

print("主文档",mainPart,root.tagName);
return parts;
```

- 文件参数是 aardio 文件路径；**部件名是包内 URI 路径**，不要用 `io.fullpath` 或 Windows 路径规则处理它。
- `listParts()` 返回 `{name,size,compressedSize,contentType}` 数组，包含 `[Content_Types].xml`，不含 ZIP 目录项。
- `listEntries()` 返回 `{name,isDir,size,compressedSize,contentType}` 数组，额外保留 ZIP 中实际存在的目录项；目录名以 `/` 结尾，不合成隐式目录。不读取条目内容。
- `readPart(name)` 返回二进制字符串；空部件为 `""`，失败为 `null,err`。读取时校验实际长度及 CRC。
- `extractPart(name,filePath,overwrite=false)` 将指定部件的原始字节导出到明确指定的文件路径。成功返回完整输出路径，失败返回 `null,err`。不做格式转换，也不是包编辑或生成文档。
- `getContentType(name)` 按 Override / Default 查找类型，未声明时返回 `null`。
- `getRelationships(sourcePart)` 返回关系数组；省略源部件表示包关系。缺少 `.rels` 合法返回空数组；无效关系或缺失内部目标返回 `null,err`。
- 关系字段为 `id,type,target,external,partName`。外部关系仅报告 `target`，没有 `partName`，不会访问其 URL 或本地资源。
- `resolveTarget(sourcePart,target)` 解析包内相对目标，禁止越出包根目录。本版明确拒绝内部目标中的查询串、片段、反斜杠和外部方案。
- 使用结束须调用 `close()`；可重复关闭。包对象不要跨线程传递。已返回的文本、清单和高层读取结果不依赖句柄。

## 原始部件导出约定

- 默认不覆盖；目标已存在时报错。明确传 `true` 才允许覆盖。源文档自身（包括可识别的同文件别名）不允许作为目标。
- 先通过 `readPart` 完成长度、CRC 与读取限额检查，再写目标目录内的临时文件，成功后重命名提交；不会先清空已有目标。读取、写入或提交失败会清理当前临时文件。
- 空部件合法，可导出为 0 字节文件。非法 Windows 文件名、保留设备名或不可写目标返回错误。
- `filePath` 是调用者明确选择的文件系统路径，**不是把包内 URI 直接拼到任意解压目录**；通用接口不自动构造输出路径。PPTX 媒体接口采用末级文件名平铺，遇到同名冲突会预先拒绝。
- 一次导出只处理一个文件；批量媒体导出不是整批事务，后续失败不回滚此前成功的文件。它会返回 `partial`，详见 [PPTX 读取与媒体](pptx/index.html)。
- 导出当前复用有大小上限的 `readPart`，会在内存中容纳一个完整部件，不是大文件流式复制。默认 16 MiB/件；需要更大的视频等资源可显式提高 `maxPartBytes`（最高 128 MiB）与 `maxReadBytes`（最高 1 GiB），超出范围明确报错。

## XML 查询约定

`readXml()` 返回 MSXML DOM 的**根元素 COM 对象**，不再是 `string.xml` 表节点。使用 `fsys.openXml.xml.isElement/child/children/attribute/text` 按命名空间 URI 与本地名精确查询，避免依赖 `p:`、`a:` 等固定前缀。属性通过 `attribute()` 读取，实体只解码一次。

MSXML 负责 XML 解码及良构性检查，支持 UTF-8、带 BOM 的 UTF-16 LE/BE 和 Unicode XML 名称。禁用 DTD、外部实体和加载时验证，不调用 Office/WPS，不做 XSD 或完整 OPC 验证。MSXML6 显式启用新解析器和属性空白规范化；具体文本换行规则与 XP 降级限制见 [XML 查询辅助](xml/index.html)。

DOM 节点只在创建它的线程内使用，关闭 ZIP 包后仍可查询。不要将它们作为普通表序列化或跨线程传递；需要跨线程结果时先提取字符串/数组，或使用返回普通数据的 `pptx.read()`、`docx.read()` 或 `xlsx.readSheet/readWorkbook()`。DOM 本身可以在内存中修改，但这些修改不会自动写回 ZIP 包。显式提交 DOM 可用 editor.setXml/save；修改后的 XML 不承诺逐字节往返一致，未修改部件则保留原始解压字节。

## 限额与失败

`open(path,options)` 及 PPTX/DOCX/XLSX 高层入口的 `options` 均支持以下安全限额。DOCX/XLSX 另有内容输出选择及矩形/文本展开限制，见各自指南；不要混为同一种限额：

| 参数 | 默认值 | 含义 |
|---|---:|---|
| `maxEntries` | 10000 | 中央目录条目数，包含目录 |
| `maxPartBytes` | 16777216 | 单个被读取部件的解压字节数，16 MiB |
| `maxReadBytes` | 134217728 | 一个包累计读取字节数，128 MiB，重复读取也累计 |
| `maxXmlDepth` | 128 | 单个 XML 最大元素深度；MSXML6 在解析时限制，解析后再检查 |
| `maxXmlNodes` | 200000 | DOM 解析后的节点计数，包含元素、文本、CDATA、注释、处理指令，不含文档节点本身及属性 |

限额必须是正整数；超限返回错误而不截断结果。节点数限制在 DOM 创建后检查，不是解析前的内存上限；包层同时限制 XML 部件字节数。按需读取意味着**未读取的媒体等部件不会做 CRC 或内容校验**，不能把成功提取文本当成整份文件通过全面校验。

传统二进制 `.doc/.xls/.ppt`、加密 Office 文件、加密/分卷 ZIP 不在支持范围。不会执行宏、嵌入对象或外部关系。不静默将损坏包、缺件或不支持的文档当成空结果。
