在跨系统数据交换中,XML凭借良好的结构表达能力被广泛使用。但当业务数据里混入了特定符号,解析器往往会直接报错并中断流程。理解解析器对字符的底层约束,是写出稳定接口的前提。

一、为什么特殊字符会让XML解析失败
XML文档本质上是一段带有严格嵌套规则的文本。解析器在读取时,会按照词法状态机逐个字符扫描:当它遇到<时,认为一个新的标签开始;遇到&时,认为一个实体引用或字符引用启动。如果这两个符号后面跟的内容不符合标签或实体语法,状态机就会进入错误分支并抛出异常。
除了<和&,>虽然在很多场景下可以原文出现,但在序列]]>或标签结束上下文里仍可能引发歧义。单引号与双引号仅在属性值定界时才需要转义。换句话说,XML不是不能放符号,而是要求破坏语法结构的符号必须以转义形式存在,否则文档就不再是良构的XML。
二、必须掌握的预定义转义字符
XML规范预定义了五个实体,用来替代会干扰解析的字符。它们分别是:<代表小于号,>代表大于号,&代表与符号,'代表单引号,"代表双引号。只要数据中出现前三个,就一定要转义;后两个仅在对应引号作为属性边界时使用。
下面是一段未转义就会导致错误的Java拼接示例,以及修正后的写法:
// 错误写法:直接拼接含有特殊字符的文本
String name = "Tom & Jerry";
String xml = "<user>" + name + "</user>";
// 解析时会因为 & 后不是合法实体而失败
// 正确写法:手动转义
String safe = name.replace("&", "&")
.replace("<", "<")
.replace(">", ">");
String xmlOk = "<user>" + safe + "</user>";
手动替换容易遗漏,实际项目应使用标准库。例如Java的StringEscapeUtils.escapeXml11会自动处理上述字符,避免人为疏忽。注意旧版escapeXml只覆盖基础五个,遇到不可见控制字符仍可能出问题,推荐用XML 1.1工具类。
三、CDATA段的使用与局限
当一段文本包含大量特殊符号,频繁转义会降低可读性。XML提供CDATA段,以<![CDATA[开头、]]>结尾,中间内容解析器当作纯文本,不做词法解析。它适合存放代码块、SQL语句等。
但CDATA不能嵌套,文本里一旦出现]]>就必须拆分或转义。此外,部分老旧解析配置会拒绝CDATA,或在转JSON时丢失边界。示例:
<sql><![CDATA[ select * from t where a < 10 and b & c ]]></sql>
如果业务数据由用户自由输入,包含]]>的概率不低,此时用转义比CDATA更安全。很多网关在转发报文前会统一把CDATA展开并转义,正是出于兼容考虑。
四、动态报文拼接与第三方清洗方案
在微服务架构里,报文常在运行时由对象序列化而成。最佳实践是禁止字符串拼接,统一采用JAXB、Jackson XML或Dom4j等库,它们内部已处理转义。以下为Dom4j写节点的例子:
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
Element root = DocumentHelper.createElement("root");
Element e = root.addElement("msg");
e.setText("价格 < 100 且 type='A'"); // 库自动转义
System.out.println(root.asXML());
对于外部传入的脏报文,可先使用容错解析器如TagSoup或正则预清洗,将孤立&替换为&后再交给标准解析器。建立统一报文网关层,集中处理收发两端的转义与编码声明,能显著降低各业务线重复踩坑的概率。
五、常见误区与排查清单
一个典型误区是认为只要页面能显示就等于XML合法。浏览器对HTML容错强,会勉强渲染,但后端严格解析仍会失败。另一个误区是混淆HTML与XML转义规则,HTML里很多实体在XML中不存在。
排查时建议遵循清单:确认文件头声明encoding与实际字节一致;用xmllint等工具做良构校验;在日志中打印原始报文而非转义后报文,便于定位漏转义位置。把这些动作固化到CI脚本,能在发版前拦住绝大多数解析故障。