跳到正文

将语义文档保存为 XML 模板 ​

XmlTemplateWriter 把支持的 Document/PageDocument/SectionedDocument 写成现有版本 1 XML,并以普通 ZIP 保存图片和调用方显式提供的字体/许可。重开后仍使用 XmlTemplate → DocumentTemplate,可以编辑源码,也可以先恢复模型再调用语义编辑与统一转换。

这是模型到既有模板语法的映射。动态模板的变量、条件、循环及片段应继续使用 XmlTemplateBundle 保存原源码;渲染后的模型没有这些编写意图。

保存、重开和编辑 ​

以下代码假定 document 是已构造的 Document,目标父目录存在,字体和许可为调用方有权分发的显式资源。

java
var prepared = XmlTemplateWriter.from(document)
        .font("Regular.ttf", fontBytes)
        .license("Font.txt", licenseText)
        .prepare();
var preview = prepared.preview();
var actual = prepared.to(Path.of("report.zip"));

var bundle = XmlTemplateBundle.read(Files.readAllBytes(Path.of("report.zip")));
var restored = bundle.compile().render(Map.of());
var edited = DocumentEditor.edit(restored)
        .replaceText("summary", "草稿", "已复核").execute();
var request = XmlTemplateWriter.from(edited.document());
bundle.fonts().forEach(request::font);
bundle.licenses().forEach(request::license);
request.prepare().to(Path.of("edited.zip"));

单节带页区文档使用 from(PageDocument) 保存,重开使用 renderWithPageRegions(Map.of())。

类型分别位于 com.mirakyux.export4j.adapter.template 和 com.mirakyux.export4j.model.edit。分节文档使用 from(SectionedDocument) 保存,重开使用 renderSections(Map.of());普通 render 不会扁平化分节,而是明确拒绝。

font 在加入时复制字节;prepare 固定所有配置、策略、报告和确切 ZIP 编码,先编译产生的 XML,再返回不可变结果。多次 to 不会再次运行编辑/编码。PREVIEW 与成功写出的 ACTUAL 保留相同 issues;I/O 失败不返回成功报告。

可保留内容 ​

ColumnFlow 正文分栏保留容器 ID、栏数、pt 栏距、平衡、宽度比例及有序段落/表格。相邻通栏表格/图片和后续分栏仍作为独立正文块保存,重开后从真实剩余页高重新排版。

范围当前映射
文档/分节标题、作者、节 ID/顺序、每节纸张/四边边距、显示页码起点
页区默认/FIRST/EVEN 页眉页脚、显式空对及正高度空区;背景/前景的固定位置、尺寸及顺序;共享图文/表格源、PAGE/PAGES 与完整样式
正文段落、六级标题、无序/十进制列表、Caption、显式换页、原文与 ID;标题/列表文字外链
段落格式字号、行高、对齐、前后间距、两种缩进、断行策略、整段保持、跟随下一块、段前分页、分页前后最少行数
富文本/列表标记完整字体角色、字号、颜色、基线位移、下划线/删除线;列表标记的样式独立于正文,保留级别、自定义 bullet 及编号重启
表格固定/比例/有界 Auto 列、重复表头、行列跨度、递归块格、拆行开关、四边 padding/前后间距、格格式与物理锚点
行内对象段落/行内格的原 PNG/JPEG、尺寸/位移/替代文字/外链;PAGE、PAGES、页码引用、编号引用及其宽度/样式
独立交叉引用CrossReference 的 PAGE/NUMBER、原始前缀、目标 ID 和完整格式;最终分页后解析跨节显示页码、题注/列表标签及跳转,多行预留沿用既有布局
注释/题注显式传入的脚注/尾注顺序、正文、种类、ID/锚点;段落显式标记宽度/样式或旧段末标记;Caption 原始 kind/text/format,编号仍在布局阶段计算
声明式导航自动当前标题及最大级别、手工书签;目录启用状态/标题/完整条目排版及标题/页码样式、三处颜色的显式意图;见导航指南
显式资源原图片字节去重;字体原字节及许可文本,文件名由调用方指定

所有几何单位为 pt。原文 UTF-16 顺序、字段/图片的 U+FFFC 位置保留。保存的 XML 使用 literal-text="true",${...}、未闭合 ${、CR/LF/TAB 和补充平面字符均按正文保存;XML 特殊字符安全转义,换行/制表使用数字实体避免属性归一化。非法 XML 1.0 Unicode 无条件失败。

已有纯文本短构造器可能在重开后表现为显式默认运行;空文本及对象运行也可被既有绑定器规范化。这不改变文字、完整格式或行内对象,但不承诺 Java record 的内部表示、主题名称/继承意图、原始 XML 排版或 ZIP 压缩字节相同。默认运行控制与富模型完整值分别验收。

XML 能表示的 Auto/递归结构不等于所有布局组合可用;含块格表格的 Auto 等既有布局限制继续适用。字体角色不会自动关联 ZIP 内的某个文件,重导出仍须显式加载并配置引擎角色。

导航属于 SectionedDocument,保存后使用 renderSections 或 renderComplete 重开;render/renderWithPageRegions 遇到导航明确拒绝。默认引擎和直接渲染器从当前候选消费声明;不序列化运行时 Navigation 的评论、表单或回调。旧构造器默认 DocumentNavigation.NONE,新增 record 组件要求调用方重新编译,见 ADR-0093。

页区共享来源 ​

页区使用既有 header/footer/box,加可选 source-id 和 source-ref 表达同类来源共享。首次写完整来源,后续只写引用;每个 box 仍保存自己的 x/y/width,height 来自来源。默认/FIRST/EVEN 声明和整层清空保持,未声明 FIRST 与显式空对不混同。完整语法和静态引用边界见页区指南。

相等来源在同类页区内只保存和绑定一次;所有源块 ID 与正文共同避让,实际放置与绘制仍分别计费。完整值包含已经解析的格式,不会在引用处重新应用主题。旧编译器不能读取新增属性,重开方需要支持该语法的版本。三轮模型/源码控制、共享编辑和实际图层输出见阶段七十九及 ADR-0085。

完整绑定与注释 ​

注释仍是显式独立数据,保存时调用 .notes(notes);不会从正文猜测。重开包含注释的模板必须使用完整入口:

java
var bound = bundle.compile().renderComplete(Map.of());
var edited = DocumentEditor.edit(bound.document()).notes(bound.notes())
        .replaceText("summary", "草稿", "已复核").execute();
var request = XmlTemplateWriter.from(edited.document()).notes(edited.notes());
bundle.fonts().forEach(request::font);
bundle.licenses().forEach(request::license);
request.prepare().to(Path.of("edited.zip"));

DocumentTemplate.Rendered 返回 document() 和不可变 notes(),支持分节及页区。旧 render/renderWithPageRegions/renderSections 遇到非空注释声明明确拒绝,避免静默丢失。bundle.conversion() 使用完整入口,传递注释到编辑器和目标;PDF 目标使用相同脚注/尾注布局。

ID 必须在注释与正文/页区块之间唯一;锚点只允许顶层正文段落、列表或 Caption。显式标记只允许普通正文段落,须唯一匹配同段 Note;未显式标记的注释继续使用段末标记。缺失、重复、错位标记或条件展开后消失的锚点无条件失败。表格和页区不放宽原限制。静态根级注释语法及数据绑定边界见XML 指南。

不支持内容与动作策略 ​

默认 ProcessingPolicy.STRICT 遇到不支持内容即失败。规则使用目标格式 XML_TEMPLATE、操作 EXPORT,通过 .policy(...) 配置共享 ERROR/WARN/IGNORE。必须选择当前功能真实支持的 treatment;要求 PRESERVE 而没有保留通道时仍失败。

feature允许的 treatment实际损失
UNSUPPORTED_BLOCKDISCARD不在映射表内的块整体移除,例如自定义运行时组件;独立 CrossReference 与行内编号/页码引用均已支持
IDENTIFIERDEGRADEXML 无法表达的块/节/列表/目标 ID 稳定改为 saved-N,已有名称避让、引用同步改名
FONT_ROLEDEGRADEXML 无法表达的字体角色清空,其他样式保持;重导出将使用默认字体组

例如,允许移除未映射块但保持其他功能严格:

java
var policy = ProcessingPolicy.builder()
        .feature("UNSUPPORTED_BLOCK",
                ProcessingPolicy.Action.WARN, ProcessingPolicy.Treatment.DISCARD)
        .build();
var prepared = XmlTemplateWriter.from(sectionedDocument).notes(notes).policy(policy).prepare();

WARN 返回 warnings 和实际 losses;IGNORE 只隐藏警告,losses 仍保留。诊断定位节、区域(如 HEADER_FIRST/FOREGROUND:0)、块、物理格路径和行内半开 UTF-16 范围。结构性 ID 损失定位到持有者;重复使用同一改名 ID 不重复产生重命名记录。注释在语义模型之外,调用方须明确传入,不会由正文推测。

统一转换与资源所有权 ​

XmlTemplateWriter.conversionTarget() 是已有 DocumentConversion 的目标;bundle.conversion() 是无绑定数据的静态模板源:

java
bundle.conversion()
        .edit(editor -> editor.replaceText("summary", "草稿", "已复核"))
        .prepare(XmlTemplateWriter.conversionTarget(bundle))
        .to(Path.of("edited-model.zip"));

无参数转换目标保存模型、注释和当前图片;conversionTarget(bundle) 及 Request.resources(bundle) 另复制指定包的字体字节和许可,不复制旧 XML/片段或已经移除的图片,也不从适配器/引擎搜集字体。单项 font/license 可继续显式覆盖。需要数据的模板先调用 compile().renderComplete(data),再使用 DocumentConversion.from(bound.document(), bound.notes());不得用空数据掩盖缺值。XML 的保留不扩大其他适配器范围:DOCX 当前不导出注释/Caption/独立 CrossReference,HTML/Markdown 对注释、题注、独立引用和字段仍按各自真实损失处理。

预算、取消和输出 ​

Writer 默认最多 10,000 个语义块及注释(正文及按同类去重的页区源)、100,000 个真实格、10,000 条 issue;.limits(...) 可配置。XML 源累计长度和图片/字体仍受 .bundleLimits(...) 控制,编译/绑定另受现有 XML 预算限制。prepare 同时编译并完整绑定生成的静态 XML,检查注释关系后才编码。达到预算、取消、非法输入或损坏资源直接失败,WARN/IGNORE 不放宽它们。

.execution(...) 接受同步取消/超时/字节预算。准备和出版都有检查点;借用输出流不关闭或刷新,物理写失败可能已写出部分字节。路径出版用同目录临时文件和原子替换,无非原子回退;发布前失败保留旧目标并清理临时文件。父目录由调用方创建。

兼容性与验收 ​

DocumentTemplate 的 record component 在 literalText 后新增 notes;旧 4/5/6/7/8 参数构造器均保留,默认注释为空,旧插值行为不变。record component 数量、反射形状及 equals/hashCode/toString 包含这些值,不能据旧构造器保留声称所有反射或序列化消费者完全兼容。Builder 支持 .literalText(...)/.notes(...),toBuilder 保留完整模式与注释快照。现有 XML 默认仍插值,只有根显式声明才关闭文本插值;if/each/rows 的控制数据路径继续有效。

独立 CrossReference 保存与绑定见XML 语法和阶段八十一。支持 replaceBlock 改写前缀/目标后再保存;动态展开后的非法 ID 必须显式报损重命名,目标同步更新。未绑定或悬空引用不生成缓存值,统一转换/布局仍执行关系验证。

注释、Caption 和标题/列表外链保存设计见 ADR-0086,实际保存、三轮完整控制、编辑和输出见阶段八十。

设计见 ADR-0084,可运行样例见 XmlModelSavingExample,独立结构、三轮控制与实际编辑输出见阶段七十八验收。标准文件的显式伴随包恢复与编辑后资源传递见恢复指南及阶段八十二;标准输出内嵌恢复、任意复杂模型恢复和更广往返仍见 GAP-24。