# DOCX 内容读取与媒体导出

`fsys.openXml.docx` 的读取/媒体入口使用共享 ZIP 包层与系统 MSXML。无需安装 Office/WPS，不调用 .NET；读取不修改源文件，媒体导出仅写入明确指定的目标文件。

读取支持 DOCX、DOCM、DOTX、DOTM 的 WordprocessingML Transitional/Strict 核心命名空间。依据真实包关系、内容类型和 XML 根元素识别，而非只看扩展名；不会执行宏。传统二进制 `.doc`、RTF、加密 Office 文档不属于读取入口。

**新建文档**可使用 `fsys.openXml.docx.write(path,document,options)`，它按需转发 `fsys.openXml.docx.builder.write`。只做生成时应独立导入 builder，避免加载读取器；详见 [Word 结构化生成](builder/index.html)。生成内容模型与下文的读取结果并不对称，不能把 read 的结果直接传入 write 期待保真复制。原有读取/媒体函数仍保持只读语义，不修改源包。

**已有文档的受控编辑**使用 `fsys.openXml.docx.editor`；它只提交 `word/document.xml`，支持普通正文的字面替换和普通未合并矩形表格填值，保留其余部件与未知内容。详见 [Word 受控编辑](editor/index.html)。

## 已有文档受控编辑

```aardio
import fsys.openXml.docx.editor;
var ed,err = fsys.openXml.docx.editor.open("/input.docx");
if(!ed) return null,err;

var n,replaceErr,detail = ed.replaceText("Name","NAME",{expectedCount=1});
if(n===null){ ed.close(); return null,replaceErr; }

var table=ed.getTable(1);
if(table && table.cells[2][1].editable){
    var written,fillErr = ed.setTableCells(1,[["完成",0]],{row=2;column=1});
    if(written===null){ ed.close(); return null,fillErr; }
}

var path,saveErr = ed.save("/output/result.docx",{overwrite=true});
ed.close();
if(!path) return null,saveErr;
return path,{replaced=n;written=written;detail=detail};
```

编辑器会话方法和安全边界见 [Word 受控编辑](editor/index.html)。它不是完整 Word 编辑器：不处理域计算、修订、内容控件、合并表、嵌套表、页眉页脚、图片或排版。

## 快速读取

```aardio
import fsys.openXml.docx;
var data,err = fsys.openXml.docx.read("/input.docx",{
    includeHeaders = true;
    includeFooters = true;
    maxParagraphs = 2000;
    maxTextBytes = 1048576;
});
if(!data) return null,err;
print(data.text);
for i,w in data.warnings print(w.code,w.message,w.partName);
return data;
```

主要结果字段：

| 字段 | 含义 |
|---|---|
| `path/mainPart` | 源文件完整路径、真实主部件名 |
| `text` | 按段落 XML 顺序合并的文本，包括表格内段落 |
| `paragraphs` | `{index,text,source,partName,storyId,styleId,outlineLevel}` 数组，后三项按实际内容提供 |
| `tables` | `{index,rows,rowCount,columnCount,source,partName,depth,storyId}` 数组 |
| `paragraphCount/tableCount` | 本次保留的段落/表格数量，不是完整文档的分页统计 |
| `sources` | 本次读取的正文及附属部件，含 `source,partName,relationshipId` |
| `info` | 保存的核心/扩展属性，缺失可选属性不会凭空填零 |
| `media` | 启用 `includeMedia` 时的媒体目录清单 |
| `warnings/truncated` | 省略/回退/截取提示，以及是否发生输出截取 |

结果只含普通 aardio 数据，可跨线程传递。内部 MSXML 节点不导出；包在成功或失败时都会关闭。`includeParagraphs=false` 仅关闭段落数组，不影响 `text`。

## 段落与附属文本

默认只读正文。设置以下选项可追加附属文本：

- `includeHeaders/includeFooters`：从正文节属性的页眉/页脚引用定位，按首次引用顺序去重追加，不扫描文件名或读取孤立部件，不按实际页面重复展开。
- `includeFootnotes/includeEndnotes/includeComments`：随后按脚注、尾注、批注的顺序追加各部件内容；部件内保持 XML 顺序，`storyId` 保留注释/脚注标识。非普通类型的脚注/尾注分隔符不作为用户文本。

正文与附属内容使用 `source="body/header/footer/footnote/endnote/comment"` 区分。这是部件文本汇总，不是页面阅读顺序，也不会在正文脚注标记位置插入注释内容。

- 同一段的多个 `w:t` 相连，保留空白、Unicode 和实体的真实文字。
- `w:tab`（文本 run 内）为 TAB；`w:br/w:cr` 为 LF，不区分显示层面的分页/分栏。段落默认用 CRLF 分隔。
- 制表位、样式等段落属性不混入正文。`styleId` 是保存的样式 ID；`outlineLevel` 是保存的 0 基值，不查询样式继承。
- 原生默认 `keepEmptyParagraphs=true`，`trimText=false`。可自定义 `paragraphSeparator`，包括空字符串。
- `readParagraphs(path,options)` 只返回段落数组；成功第三返回值是警告数组，失败 `null,err`。

## 表格

```aardio
import fsys.openXml.docx;
var tables,err,warnings = fsys.openXml.docx.readTables("/input.docx",{
    maxTables = 20;
    maxRows = 200;
    maxColumns = 30;
    cellParagraphSeparator = '\n';
});
if(!tables) return null,err;
for i,t in tables print(t.source,t.partName,t.rowCount,t.columnCount);
return tables;
```

- `rows` 为物理单元格二维文本数组。`rowCount/columnCount` 为返回行数与最大物理单元格数，不展开 `gridSpan/vMerge/hMerge` 合并格，也不声称还原矩形网格。
- 单元格内部多个段落用 `cellParagraphSeparator` 合并，默认 LF；`trim` 默认 false。
- 嵌套表按外表在前的顺序单独列出，`depth` 从 1 开始。外层单元格也包含内表文本，分析所有表时应避免重复计算；汇总正文的段落则只收录一次。
- `maxRows/maxColumns` 只限制表格输出，不裁剪汇总正文的段落。
- 此基础库不生成以首行为字段的对象表。需要旧式 `firstRowAsHeader/asObjects` 时，可使用 `autos.skills.word.readDocxTables`；它在基础读取结果上调用既有 `rowsToTable`，不另做 XML 解析。

## 属性

```aardio
import fsys.openXml.docx;
var props,err = fsys.openXml.docx.getProperties("/input.docx");
if(!props) return null,err;
return props;
```

按包关系定位核心/扩展属性，支持非固定路径或命名空间前缀。常用字段：`title,subject,author,keywords,description,lastModifiedBy,created,modified,revision,application,appVersion,company,template,pageCount,wordCount,characterCount,lineCount,paragraphCount`。

日期与修订号保留字符串；页数等数值来自保存的元数据，可能陈旧，库不重新计算。数值 0、空字符串与缺失值区分。没有可选属性关系合法返回仅含 `path` 的对象；有关系但部件损坏、缺失或指向外部则报错。

## 清单与媒体

- `listEntries(path,pattern,options)`：验证主部件后返回 ZIP 元信息 `{name,isDir,size,compressedSize,contentType}`，包含实际目录项。`pattern` 为 aardio 模式串，不读取全部正文附属部件或媒体内容。
- `listMedia(path,options)`：返回全部目录媒体 `{partName,name,ext,size,compressedSize,contentType}`；`name` 是末级文件名，`ext` 不带点。
- 当前媒体范围为 `word/media/` 及主部件同级 `media/`，包含子目录、未引用文件，不含目录项。不保证发现任意路径上的全部关系资源，不访问外链。

```aardio
import fsys.openXml.docx;
var result,err,partial = fsys.openXml.docx.extractMedia("/input.docx","/media",{
    overwrite = false;
    maxMedia = 50;
});
if(!result) return null,err,partial;
return result;
```

导出结果：`outputDir,files,skipped`，限额截取时另有 `truncated=true`。文件项还含 `source`（包内 URI）与 `path`（完整输出路径）。

- 默认不覆盖；已有普通文件记入 `skipped`，这些资源不再读取或做 CRC 检查。
- `maxMedia` 在导出时限制成功个数，跳过不计数；在 `read` 中限制媒体清单输出；单独 `listMedia` 不截取清单。
- 平铺同名（忽略大小写）在写任何文件前拒绝；非法设备名/源件覆盖由共享 `extractPart` 边界拒绝。
- 每件先校验长度、CRC、大小限额，再写临时文件并提交。失败 `null,err,partial`，之前成功项保留；不是整批事务。
- 只复制原始字节，不执行、转码或验证媒体格式。默认每件 16 MiB、累计读取 128 MiB；可按通用包规则显式提高限额。当前在内存中容纳一个完整部件，不是无限制流式导出。

`fsys.openXml.media` 是 PPTX/DOCX 共用的小型目录/导出辅助，接收已打开的包；使用者通常直接调用上述 DOCX 入口，不必管理包生命周期。

## 选项和失败行为

| 选项 | 原生默认 |
|---|---|
| `includeText/includeParagraphs/includeTables/includeProperties` | true |
| `includeMedia` | false |
| `includeHeaders/includeFooters/includeFootnotes/includeEndnotes/includeComments` | false |
| `keepEmptyParagraphs` | true |
| `trim/trimText` | false |
| `maxParagraphs/maxTables/maxRows/maxColumns/maxTextBytes/maxMedia` | 不限制输出；设为非负整数，0 表示不取 |

内容限额限制返回结果，不保证停止 DOM 解析；截取通过 `truncated` 和 `warnings` 报告，`maxTextBytes` 保证不生成半个 UTF-8 码点。数组专用入口的第三返回值是警告，不是业务错误。

通用 `maxEntries/maxPartBytes/maxReadBytes/maxXmlDepth/maxXmlNodes` 仍是安全限额，超限直接失败而非截取，见上级指南。合法空内容不等于解析失败：坏 ZIP/XML、错误文档类型、缺失主部件/body、已读取关系无效，都会返回 `null,err`。

## 有意支持的范围

- 基础段落、常见内容控件/自定义 XML 内容容器、链接显示文字、普通表格和嵌套表。
- 修订仅处理插入/移入与删除/移出容器，不是完整接受/拒绝修订视图；行/单元格属性修订、隐藏格式、样式继承不还原。
- 域仅读取已保存文本结果，不执行指令，不计算页码、目录、公式或自动编号标签。
- 不读取文本框/图形显示文本，不做图片 OCR、公式、altChunk 转换或完整 MC 解释。`AlternateContent` 只采用 Fallback 并警告。
- 警告不是完整遗漏清单，读取/清单成功也不代表整包通过所有 CRC/XSD 校验。

## 旧 Word 技能入口

`autos.skills.word` 0.3.0 的 OpenXML 读取/媒体函数继续兼容转发；新增 `writeDocx` 转发结构化生成、`editDocx` 转发受控编辑会话。旧读取入口保留跳过空段、表格 trim=true、内容限额 0 表示未指定、媒体默认覆盖，以及 `method="zip"/formattedSize/path` 等字段。

原有 Office/WPS COM 函数及自动分流实现未改变。新的基础读取、受控编辑和结构化生成优先使用本库；复杂编辑、保真另存或 PDF 导出继续使用技能包 COM 路线。
