ADR-0029:XML 原生字段、编号与被动外链
- 状态:采纳。
- 日期:2026-10-16。
- 关联:XML 模板 API、行内字段、ADR-0025。
问题
既有 XML 模板可以声明报告结构、动态行、样式及注册图片,但最终页码、编号引用和外链只能由 Java 另行补写。直接接受 Word 指令、动态 URL 或执行表达式会扩大信任边界;字段宽度和循环目标不明确时还可能破坏最终分页及来源。
决策
扩展版本 1 白名单,仍只编译到原有 DocumentTemplate/Document,不增加布局器、数据绑定器或第三方依赖。四个空元素 page-number、page-count、page-reference、number-reference 映射到 InlineField 的 PAGE、PAGES、REFERENCE_PAGE、REFERENCE_NUMBER。字段须显式预留 width,复用模型的正值、有限值和上限校验;样式属性与 span 相同,只选择显式字体角色,不合成字体。
字段仅用于段落及语义单元格。每个字段由模型保存一个 U+FFFC 原文位置,绑定后重新计算 UTF-16 索引;最终布局决定显示页码、总页数、目标页码和编号。字段值只替换预留轮廓,不能改变既有换行/分页。值超宽、目标不存在或没有编号均沿用明确失败,不把缓存值当作动态结果。
目标为受限的静态块 ID,禁止插值与 @。循环中的静态引用只指向绑定文档中实际存在的静态目标,不自动重映射到生成的迭代 ID。条件可以影响目标是否存在,因此目标存在性留在绑定后的最终布局验证,编译不替调用方选择分支。所有分支仍须通过字段语法、尺寸、样式及目标格式检查。
为了让 XML 自身可以声明有效的编号目标,增加最小 numbered 元素,映射到 NumberedItem.rich。list-id 必填且静态;level 默认为 1,只支持 1;restart 默认为 0,非负 int,其余格式复用段落/bullet。它只接受文字和 span,不引入多级编号语法、字段或图片。递增及重启沿用现有语义编译,包括溢出失败。
外链采用 link 容器,必填静态 href,内容仅为文字、span 和注册图片。每个生成的文字运行/图片携带同一个被动 URI。href 通过现有 ExternalDestination 校验,不额外承诺其未禁止的 URI 形式。链接正文与图片 alt 可以插值,href 禁止插值;不支持链接嵌套、链接内字段、空 link、脚本或 URL 资源加载。图片的来源仍只有显式资源注册表。
预算与诊断
不绕过源 UTF-16 长度、元素、深度或解码文字预算。链接节点及其子节点均计入源元素,字段计入同一源预算;属性仍由总输入长度约束。字段、文字和图片通过现有 DocumentTemplate 逐项绑定并计入访问工作;静态 URI、alt、target 经过相同字符串计费。图片和字段仍分别遵守单段 1,024 个限制。绑定不积累独立链接注册表或外部资源缓存。
新增元素的非法值在自身 XML 行列报告 TemplateException,消息不回显地址、目标、模板正文或参数。数据绑定及最终布局仍使用原有异常和来源契约,不新增展开位置到 XML 行列的映射。编译/绑定不写文件或发起网络请求;可访问的 loopback 链接也仅保存为语义数据。
影响与验证
XML 可以直接表达带最终页码、编号目标、图文外链的报告。现有无字段/链接模板保持语法与模型结果;原 xmlTemplates 样例及其独立验证保留。扩展需另外验证模板绑定、动态表格、UTF-16 索引、最终字段值、链接矩形、目录偏移、宽度失败和全部新渲染页。
本决策不扩大后端能力。DOCX 重复表头 PAGE 仍明确拒绝;SVG/PNG 仍拒绝非空交互数据;任意指令、注释标记、多级编号、动态 URL、include、宏及脚本仍不支持。源码、测试和文档自主编写,采用 Apache-2.0,没有新增运行依赖、字体或第三方素材。