Javascript is required
错误排查发布于 2026-07-28审校于 2026-08-084 分钟阅读

XML Formatter:HTML 实体、字符引用与二次转义

&&、HTML 的   和 CDATA 都不是同一种表示。XML parser 读取实体后通常得到字符 &,serializer 再输出时会重新转义;如果把已经序列化的 & 当成普通数据再次转义,就会得到 &。本文只讨论实体表示和二次转义。

XML FormatterXML EntitiesHTML EntitiesCDATA

一、问题概述:同一个字符有源码表示和解析值

XML 文本 A & B 的解析值是 A & B;如果业务数据本来就包含六个字符 &,它被 XML 转义后应变成 &,因为这时 & 是数据而不是实体语法。判断是否二次转义,必须先明确当前字符串处于解析值还是序列化文本层。

二、最小复现:解析一次再序列化一次

下面用纯函数展示实体编码层次。它没有调用某个 XML 库,目的是让输入值和输出源码分开可见。

function escapeXmlText(value: string): string {
  return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
}

const parsedValue = "A & B";
const literalText = "A &amp; B";
console.log(escapeXmlText(parsedValue)); // A &amp; B
console.log(escapeXmlText(literalText)); // A &amp;amp; B

三、根因:二次转义发生在错误的层边界

常见链路是 parser 将实体解码为字符,formatter/serializer 再对字符编码一次,这是正确的;错误链路是业务代码先手工生成 &amp;,随后 serializer 把它当普通字符再次编码。HTML 实体表还包含不属于 XML 预定义实体的名称,不能直接复制到 XML。

四、推荐方案:内部保存解析值,最后只序列化一次

内部模型保存实际字符,不保存带 &amp; 的 XML 源码片段。由一个明确的 serializer 负责文本/属性转义;如果必须接收已序列化片段,应使用不同的类型或拒绝混入,不能让同一个 string 同时代表两层数据。

五、完整代码:区分原始值、实体文本和 CDATA

以下代码只允许 serializer 接收解析值;CDATA 分支不做实体转义,但仍需处理 ]]> 边界。HTML &nbsp; 不会被当作 XML 合法实体自动保留。

type XmlText = { kind: "text"; value: string } | { kind: "cdata"; value: string };

function serializeXmlText(value: XmlText): string {
  if (value.kind === "cdata") return "<![CDATA[" + value.value.replaceAll("]]>", "]]]]><![CDATA[>") + "]" + "]>";
  return value.value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
}

console.log(serializeXmlText({ kind: "text", value: "A & B" })); // A &amp; B
console.log(serializeXmlText({ kind: "text", value: "&amp;" })); // &amp;amp;
console.log(serializeXmlText({ kind: "cdata", value: "A & B" })); // <![CDATA[A & B]]>

六、常见错误方案

把 HTML 实体 &nbsp; 直接写入 XML,可能遇到未声明实体;把 &amp; 当作解析值再调用 escape,会产生二次转义;把 CDATA 内容也做实体替换,则破坏了 CDATA 的用途。手工替换 & 的顺序错误,还可能把新生成的实体再次匹配。

七、边界条件:属性、数字引用与外部实体

属性和文本都需要转义,但可转义字符集合和引号边界不同。&#38;&#x26; 是数字字符引用,解析后与 &amp; 的字符值可能相同,源码表示却不同。外部实体、DTD 和安全策略属于 parser 配置,不应由 formatter 擅自展开。

八、如何验证没有二次转义

分别测试原始 &、字面量 &amp;、数字字符引用、HTML &nbsp;、属性值和 CDATA。对每种输入记录解析后的字符,再序列化并重新解析,断言字符值和节点类型正确;不要只对比一次输出字符串。

九、FAQ

问:看到 &amp;amp; 一定是 bug 吗?答:如果业务值本来就是 &amp; 六个字符,它可能是正确的;要看输入层语义。

问:HTML 实体能在 XML 中使用吗?答:只有 XML 已声明的实体可直接使用,HTML 实体名称不能默认照搬。

问:CDATA 是否完全不需要转义?答:普通实体不需要,但 ]]> 必须拆分,且 XML 禁止某些控制字符。

十、总结

XML formatter 应在解析值与序列化文本之间只做一次转义。区分 XML 预定义实体、数字字符引用、HTML 实体和 CDATA,避免把已编码片段当普通值重复编码,并用解析—序列化—再解析验证字符值。

来源与延伸阅读

技术审校所依据的规范与权威参考资料。

相关文章

继续阅读

可打开关联的浏览器工具,使用自己的样本验证文中的处理流程。

打开关联工具