# 模板、页结构与表格数据（0.11.0+）

本阶段聚焦 **同文稿复制/删除幻灯片、保留格式的表格填值、矩形合并**。这些操作全部由 `fsys.openXml` 原生完成，无需 Office/WPS/.NET。复制已有原生图表时可独立复制图表与内嵌工作簿；**创建图表、改图表数据、跨文稿母版导入、近似预览不属于本阶段**。

完整示例：`~/examples/File/OpenXML/pptx-data.aardio`。先生成模板，复制第二页并填入另一组数据，删除临时页，再合并表格单元格。数据是虚构演示值。

## 最小完整例子

```aardio
import fsys.openXml.pptx.builder;
import fsys.openXml.pptx.editor;
var source,err=fsys.openXml.pptx.builder.write("/data-template.pptx",[
    {notes="原始模板";shapes=[{
        kind="table";name="Sales";x=60;y=120;width=720;height=180;
        fontSize=20;header={fill="17324D";color="FFFFFF"};
        rows=[["季度数据","",""],["项目","实际","启用"],["A",0,false]];
        merges=[{row=1;column=1;colSpan=3}];
    }]},
    {shapes=[{text="临时页，将被删除"}]}
]);
if(!source)return null,err;
var doc,err=fsys.openXml.pptx.editor.open(source);
if(!doc)return null,err;
var copy,err=doc.copySlide(1); // 副本现在位于第2页
if(!copy){doc.close();return null,err;}
var count,err=doc.setTableCells(2,"Sales",[["B",120,true]],{row=3;column=1});
if(count===null){doc.close();return null,err;}
var count,err=doc.deleteSlide(3); // 页号总是按当前顺序
if(count===null){doc.close();return null,err;}
var output,err=doc.save("/data-filled.pptx");
doc.close();
return output,err;
```

默认不覆盖源件或已有输出。需要覆盖测试输出时可显式传 `overwrite=true`；无论如何都不能覆盖编辑会话的源文档/可识别硬链接别名。

## 复制页

`doc.copySlide(page,insertAt)` 返回 `{index,id,partName,relationshipId,copiedParts}`。

- `page` 是复制前的当前页号，`insertAt` 是复制后的位置，均从1开始；默认紧接原页。最多1000页。
- **同一文稿内复制**：新页获得新 slide ID、关系和部件路径。形状ID只在本页内作用，保留形状名、样式、动画与切换。
- 备注及其回指、图片、常见图表/内嵌工作簿独立复制。副本内部原本共用的资源仍保持共用，但与原页隔离。
- 沿已支持关系复制常见音视频、OLE、Diagram、chart style 等依赖的原字节；不解释或执行它们。除已实测的常见图表外，不承诺所有复杂对象都已渲染验收。
- 同文稿的原版式、母版、主题继续共享；这不是母版导入器。
- 自跳转指向新页；指向其他页的链接仍指向原目标；外链仅保留，从不访问。
- 有节时新页加入插入位置前一页的节；插到最前时加入原首页的节。不会自动加入自定义放映。
- 未支持的私有依赖（例如批注/控件/厂商扩展）明确拒绝，不静默丢弃、不假装复制成功。
- 页码字段缓存、缩略图和文档应用统计不在编辑器中重算；由 Office/WPS 打开时刷新。

## 删除页

`doc.deleteSlides([1,3],options)` 原子删除指定页，返回删除数；`deleteSlide(page,options)` 是单页便捷方法。数组的页号都指 **调用开始时** 的当前页序；重复、越界和空洞拒绝。空数组返回0；普通演示允许删到零页。

- 删除幻灯片 XML、独立备注及相应关系/类型记录，不只是从页序中隐藏。
- 其他页仍链接被删页时，**默认拒绝**。只有显式 `{links="remove"}` 才移除支持的 `hlinkClick/hlinkMouseOver`，文字与形状本身保留。
- 节、自定义放映、大纲视图中已支持的页引用同步处理；如果会把自定义放映变为空则拒绝，需先在 Office/WPS 中调整该放映。未知引用（例如部分缩放/扩展）即使 `links="remove"` 也拒绝。
- 扫描全部关系以避免留下已识别的悬空关系，不按 slide 文件名猜页序。
- **不全包清理孤儿媒体、图表、内嵌工作簿或未知对象**，避免误删共用内容。因此删除不是敏感数据擦除工具，被删页使用过的媒体/图表数据可能仍留在包中。
- 一次方法内先校验全部改动再提交；失败没有半页/半表修改。连续多个方法不是统一的大事务，最终保存由调用者决定。

## 查表与填值

`getTable(page,target)`：`target` 为表格形状ID或唯一名称。返回 `id,name,rows,merges,rowCount,columnCount,columnWidths,rowHeights`，宽高单位point。`rows` 是矩形物理网格文字，合并覆盖格不缩掉。

`setTableCells(page,target,values,{row=1;column=1})` 填矩形区域；`setTableCell(page,target,row,column,value)` 填一格。

- 保持表格行列、尺寸、边框、填充、布局和未写入格；不增加行列、不自动缩字。内容过多时 Office 仍可能自动撑高表格。
- 字符串、数值、布尔值转为文本；`0`、`false`、空串各自保留。`null` 是跳过，`""` 是清空。返回被写入的逻辑格数（0合法），失败 `null,err`。
- 写入合并锚点正常；覆盖格只允许 `null/""` 占位并跳过，非空值拒绝。不把对覆盖格的写入偷偷重定向到锚点。
- 二维数组必须矩形；尾部空项请明确写空串。不接受样式表/公式对象作为数据值。
- 新文字继承原格首段与首运行格式；多段用 CR/LF，软换行用 VT (`'\v'`)，TAB和首尾空格保留。
- 新数据替代旧运行、字段及运行链接，**不会把旧链接目标留给新数据**。其他段落/运行的复杂格式不保留；需要复杂富文本请用生成器。
- 目标歧义、非法XML字符、越界或某个值失败时，整个本次填表操作不提交。

## 合并单元格

生成阶段：`kind="table"` 的 `merges=[{row,column,rowSpan,colSpan}]`。行列从1开始，跨度默认1，至少覆盖两格；保持完整 `rows` 网格，被覆盖格必须为空。越界/重叠/隐藏非空数据拒绝。

编辑阶段：`mergeTableCells(page,target,{row=1;column=1;rowSpan=2;colSpan=2},options)`。

- 横向、纵向、矩形合并共用同一拓扑解析；布局、生成与预检不各写一套。
- 默认 `content="error"`，覆盖格非空就失败，避免丢失数据。
- 显式 `content="append"` 时把各格段落按行优先追加到左上格，保留段落/运行格式，再清空覆盖格。空锚点不会额外制造开头空段。
- 不支持与已有合并重叠、拆分单元格、不规则区域；行高/列宽/表格尺寸不变。
- 预检按合并宽度与跨行总高测量；跨行自动增高量是保守估算，不保证 Office/WPS 的逐行分配相同。

## 边界

仅支持合法 DrawingML 原生矩形表格，不把图片、SmartArt或图表冒充表格。已有不一致的合并标记拒绝。

原生生成样本、编辑往返、未改部件字节、失败原子性及关键 Office 属性均有自动测试。Office 产生的复杂图表可能带旧版 SDK 不识别的扩展；复制应保留原字节并比较源件诊断，不能删除扩展来伪造零错误。WPS/其他 Office 版本仍需用实际交付样本验收。
