摘要

  • RFC 3076 并不直接规范化一份未触碰的文件。字节流必须先由 XML 处理器转换成 XPath 节点集,换行、属性、CDATA 与已解析实体在此之前就已发生变化。
  • 相同的规范化字节,是对特定数据模型、注释选项和算法的精确回执;它不能还原原始 XML,也不会自动保留 DTD、基础 URI 或应用语义。

稳定输出从链条中段开始

2001 年 3 月,RFC 3076 与 W3C 的 Canonical XML Version 1.0 Recommendation 面对一个真实难题:XML 允许多种物理写法表达许多应用眼中的同一信息。属性顺序可以不同,字符编码可以不同,空元素可用短写或成对标签,字符与实体引用也可以换成对应内容。数字签名或完整性比较若直接面对这些表面差异,就难以得到一致的摘要。

规范化方法把输出固定为 UTF-8,删除 XML 声明和文档类型声明,把空元素改写为起止标签,统一属性引号,按规定转义字符,移除多余命名空间声明,并按字典序排列命名空间与属性。是否保留注释是明确参数。一个良构的规范化结果再次经过同一方法时不会改变。

但规范的正式对象是 XPath 节点集。输入若是 XML 字节流,系统必须先解析,再得到节点。规范化器接手时,原文件已经被另一套规则加工过。

解析器已经决定了什么可以被排列

XML 处理器先统一换行与属性值,把 CDATA 段替换成字符内容,解析字符引用和已解析实体,还可能根据 DTD 加入默认属性。命名空间成为节点,相邻字符被合并成文本节点。这些不是最终序列化时才做的排版,而是构造规范化对象的前置步骤。

因此,两份源文件可以正确地汇聚到同一输出。一份直接写字符,另一份写数字引用;一份调用内部实体,另一份写实体替换文本;一份用空元素短写,另一份用完整起止标签。只要解析后的信息符合规则,它们就可以产生相同字节。这正是 Canonical XML 的价值。

代价是输出不再可逆。原始编码被 UTF-8 取代;XML 声明和 DTD 消失;实体与 CDATA 的边界不复存在;属性原顺序改为规定顺序。规范化结果能够回答“这个节点模型的确定性表示是什么”,却不能回答“最初是哪串字节、哪些外部资源和哪套解析策略构造了它”。

完整回执必须从规范化之前开始:收到的字节及哈希、获取 URI 与时间、声明编码、解析器版本与设置、是否验证、外部子集和实体策略、实际取回的每份依赖、有效基础 URI,以及解析器加入的默认属性。只保存最终哈希无法事后恢复这些事实。

字节相同,运行行为仍可能丢失

RFC 3076 的限制章节明确列出 XPath 模型无法保存的内容:基础 URI,特别是外部已解析实体替换文本的来源位置;notation 与外部未解析实体;以及 DTD 中声明的属性类型。

基础 URI 的问题最直观。外部实体可能包含相对 URI。解析器把实体内容嵌入主文档后,若没有保留原实体的位置,这串相对字符可能按新的基础位置解析。规范化字节仍然稳定,后续应用打开的资源却可能不同。RFC 建议在需要时正确使用 xml:base,或在规范化前把相对 URI 解析成绝对 URI。

DTD 还形成另一种不对称。默认属性可能已被加入节点集,因此会出现在输出中;但提供这个默认值的文档类型声明已经被删除。与此同时,ID、IDREF、枚举、NOTATION 等类型约束不会随规范化文件继续存在。后来的解析器看见字符值,却未必知道原先的类型契约。

notation 与未解析实体也可能把外部非 XML 数据绑定给应用。XPath 模型并不携带所有绑定。RFC 称这些情况“不常见”,并不等于依赖它们的系统可以放弃来源证据。

节点集不是默认包含一切的子树

对文档子集做规范化时,XPath 提供的是独立节点组成的集合。选中元素不等于自动选中它的属性、文字与后代。父节点在集合中,也不会让被排除的子节点出现在输出里。反过来,被省略的祖先仍可能贡献命名空间环境或可继承的 XML 属性。

RFC 因而把保持成员完整语义的责任交给节点集创建者。子集的规范化结果甚至可能不是良构 XML。它仍是算法定义的字节流,却不能仅凭“canonical”一词推断完整、自足或适合再次解析。

这与 RFC 3075 的问题不同。RFC 3075 关注签名究竟覆盖哪些 Reference 与变换结果;RFC 3076 关注签名或比较以前,解析器究竟构造了什么对象。即使签名验证完全正确,也会继承节点集的来源边界。

“规范化”不是所有语义的同义词

RFC 3076 没有把规范化形式定义成所有逻辑等价的“当且仅当”条件。某个应用可能忽略特定空白,也可能把 black 与 rgb(0,0,0) 视为同一颜色;别的 W3C 规范也可能增加等价规则。因此,两份规范化输出不同,仍可能在某个应用里含义相同。

方法也不执行通用的 Unicode 字符模型规范化。它刻意保留输入中的命名空间前缀,因为盲目改名可能破坏属性值或文本中嵌入的 XPath、QName 式字符串。RFC 提供的是可执行、可测试的有限比较,不是普遍语义引擎。

后来的修订再次暴露上下文边界

W3C 在 2006 年记录了 C14N 1.0 的子集问题:把相对 xml:base 简单复制到选中后代,可能丢失祖先路径,因而改变有效 URI。后来出现的 xml:id 也表明,标识符不应像普通 XML 命名空间属性那样被继承。

2008 年成为 Recommendation 的 Canonical XML 1.1 对此作出修订:不继承 xml:id,并对 xml:base 做专门路径合成。这是规范演进的证据,不是每个 C14N 1.0 部署都失败的证据。Exclusive XML Canonicalization 解决的是片段移入新上下文时的命名空间问题;RFC 3275 把 C14N 1.0 纳入 XML Signature 的必备方法。它们都没有把规范化哈希扩张为原文件、外部输入可信度或应用授权的证明。

历史记录应保存哪条链

应保存收到的字节、哈希与获取位置;媒体类型与编码;解析器身份、版本和设置;验证方式;实际使用的 DTD、目录、外部子集与实体;基础 URI;命名空间处理与默认属性;XPath 选择及得到的节点集;注释选项;规范化方法与版本;最终输出哈希。

随后还要记录比较或签名结果、应用自己的等价规则、采取的决定与可观察效果。Lu Heng 后来关于 running code 与现实层次的论述可作为披露后的分析镜头:能够重复生成的字节,比一个抽象标签更强;但执行层回执不会吞并来源、权威、含义与结果层。

来源