XML 报错「标签未闭合」?well-formed 的五条铁律
上周帮人调 SOAP 接口,对方报文回来一段压缩成一行的 XML,解析器直接抛 Unclosed tag。我对着那串字符看了十分钟,最后发现是某个 <ns2:Item> 在两百行外少写了一个斜杠。把报文丢进格式化工具一展开,第五行就看出来了。
XML 解析失败,十次有八次不是逻辑问题,是「格式不良好」。well-formed(格式良好)是 XML 规范里写死的五条硬规则,违反任何一条,解析器当场罢工。下面这五条,记住能少掉一大半头发。
第一条:只能有一个根元素
整份 XML 必须包在唯一一个根元素里,不能出现两个平级的顶层标签。
<!-- 错:两个根元素 -->
<user id="1">张三</user>
<user id="2">李四</user>
<!-- 对:外加一个根 -->
<users>
<user id="1">张三</user>
<user id="2">李四</user>
</users>
这条最容易被忽略的场景是从接口拼接数据,循环输出时忘了外层包一层 <list>,解析器看到第二个 <user> 就抛 junk after document element。
第二条:所有标签必须闭合
两种闭合方式都行,成对闭合 <a></a> 或自闭合 <a/>,但不能只开不关。
<!-- 错:<note> 没闭合 -->
<note>
<to>小明</to>
<body>你好
</note>
<!-- 对 -->
<note>
<to>小明</to>
<body>你好</body>
</note>
自闭合写法在无内容元素上常见,比如 <br/>、<img src="x"/>。注意 HTML 里的 <br> 在 XML 里非法,必须加斜杠。SVG、Android Manifest 里写成 <View> 不带斜杠,解析器会一路吃下去,直到遇到一个意料之外的标签才报错,定位很费劲。
第三条:严格区分大小写,不能交叉嵌套
XML 区分大小写,<Name> 和 <name> 是两个标签,必须配对。标签也不能交叉,必须严格包含。
<!-- 错:交叉嵌套 -->
<a><b>内容</a></b>
<!-- 对:严格嵌套 -->
<a><b>内容</b></a>
这条踩坑的场景是从 SOAP、RSS 这种深层 XML 里找错。格式化展开后,缩进错位的那个位置基本就是交叉点。交叉嵌套的报错信息往往指向后一个闭合标签,别被带偏,真正的问题在前面。
第四条:属性值必须加引号
属性值可以用单引号或双引号,但不能省略。
<!-- 错:version 没加引号 -->
<config version=1.0 debug=true>
<!-- 对 -->
<config version="1.0" debug="true">
从 HTML 复制属性过来常踩这个坑。HTML 容忍 debug 这种裸属性,XML 一律报错。布尔值也得写成 debug="true",没有「省略值即 true」这种写法。
第五条:特殊字符必须用实体转义
文本内容或属性值里出现 &、<、>、'、",必须替换成实体。
| 字符 | 实体 |
|---|---|
& | & |
< | < |
> | > |
' | ' |
" | " |
最坑的是 & 和 <。比如想把一段说明写成 A & B,得写成 A & B。再比如想把一段示例代码塞进 XML 里:
<!-- 错:< 被当成标签起点 -->
<example>if a < b then print()</example>
<!-- 对 -->
<example>if a < b then print()</example>
解析器看到 < 就以为要开新标签,结果找不到匹配,报 not well-formed。> 和引号在文本里通常不会触发报错,但规范要求转义,老老实实写更安全。
如果一整段都不想手动转义,用 CDATA 包起来:<![CDATA[ if a < b then print() ]]>,里面的内容解析器原样保留,HTML、SQL 片段常这么塞。
顺带分清:well-formed vs valid
well-formed 是「语法对」,只要满足上面五条就是格式良好。valid 是「语义对」,除了格式良好,还得符合 DTD 或 XSD 里定义的元素结构、属性类型、出现顺序等约束。
<!-- well-formed 但不一定 valid -->
<person>
<age>张三</age>
<name>28</name>
</person>
上面这段格式完全正确,解析器不会报错。但如果 XSD 规定 name 是字符串、age 是整数且 name 必须出现在 age 之前,那它就不 valid。日常开发里碰到的大多解析器只查 well-formed,valid 校验需要专门启用,比如 Java 的 SchemaFactory、Python 的 lxml 带 schema 参数。
命名空间一句话
如果遇到 <ns:Name> 和 <xs:Name> 这种带前缀的标签,那是 XML 命名空间机制,靠 xmlns 属性声明,用来区分不同来源的同名标签。它不改变 well-formed 的五条规则,前缀也要严格闭合,只是多了个声明。
实操:先格式化再排查
XML 出错时报错信息常指向某一列,手动数列数效率极低。把内容丢进 UPTools XML 格式化工具,工具会自动展开缩进并提示未闭合的标签位置,配合错误信息很快能定位。本地解析,不传数据。
三句话总结
XML 报错多半是 well-formed 没过:单根、必闭合、不交叉、属性加引号、特殊字符转义,五条里挑错。well-formed 只是语法对,valid 还要看 DTD/XSD 约束,日常解析器一般只查前者。出错时先格式化展开再读报错位置,比对着压缩字符串数字符快得多。
相关文章