关于 XML 格式校验
XML 校验工具用浏览器自带的 XML 解析器解析文档,判断标记是否良构。良构时,报告会给出根元素名称和文档中的元素总数;不良构时,则显示解析器的错误信息,包括它放弃的那个行号和列号。
XML 有两套彼此独立的「正确」概念,把它们混为一谈会白白浪费排查时间。良构说的是语法:有且仅有一个根元素、所有标签闭合且大小写严格对应、属性值加引号、保留字符已转义、命名空间前缀在使用前已声明。有效说的是内容相对于某份模式的符合程度——XSD、DTD 或 RELAX NG——它规定哪些元素可以出现在哪里、以什么顺序、用什么类型。本工具把第一个问题回答得很完整,对第二个问题则完全不作答;当消费方拒收了一份文档、而你还不知道问题属于哪一类时,这正是你需要的。
如何使用这个开发者工具
- 把文档粘贴到 XML 面板,连同 XML 声明和 DOCTYPE 一起粘进来。
- 留意面板头部的状态:它随输入实时重新解析,最后一个标签闭合的瞬间状态就转为有效。
- 运行校验,得到包含根元素名称和元素总数的报告。
- 解析失败时跳到报告的行列位置,然后往回读——缺引号或标签没闭合,往往在解析器察觉的位置之上好几行。
- 语法干净之后,再用能读懂你那份 XSD 的处理器单独跑模式校验和业务规则校验。
实例:良构,但依然是错的
这份订单文档在语法上无可挑剔:声明了默认命名空间、嵌套正确、属性都加了引号、元素都已闭合。校验工具会接受它,而模式校验仍然会拒绝它。
<?xml version="1.0" encoding="UTF-8"?>
<order xmlns="urn:example:orders" id="A-1001">
<customer>Ada Lovelace</customer>
<line sku="DT-100" qty="2"/>
<line sku="DT-220" qty="one"/>
<total currency="EUR">59.00</total>
</order>XML 合法
根元素:order
元素数量:5数量 5 把根元素自己也算了进去:order、customer、两个 line,加上 total。更要看清报告说不了什么——qty="one" 根本不是数字,而把该属性声明为 xs:integer 的 XSD 会当场拒收这份文档。良构讲的是尖括号,不是语义。
哪些规则在这里被检查,哪些必须靠模式
第一组属于语法,任何合规的 XML 解析器都会强制执行。第二组属于内容,需要一份描述你这个格式的文法。
| 规则 | 良构检查 | 模式有效性 |
|---|---|---|
| 有且仅有一个根元素,其外只允许注释和处理指令 | 这里会检查 | — |
| 每个开始标签都有配对的结束标签,且大小写完全一致 | 这里会检查 | — |
| 属性值加引号,同一元素上不出现重复属性 | 这里会检查 | — |
| 保留字符已转义,且每个实体引用都能解析 | 这里会检查(字面与号要写成 &) | — |
| 每个命名空间前缀已在本元素或祖先元素上声明 | 这里会检查 | — |
| 子元素按要求的顺序和次数出现 | — | XSD、DTD 或 RELAX NG |
| 某个属性必须是整数、日期或枚举列表中的值 | — | XSD |
| 命名空间 URI 恰好是消费方期望的那个,而不只是「某个合法 URI」 | — | XSD 或应用代码 |
| 两个字段相互吻合,例如总额等于各行金额之和 | — | Schematron 或应用代码 |
常见问题排查
解析器报告某个命名空间前缀未定义。
成因 某个元素或属性用了 dc: 或 soap: 这类前缀,但本元素及其祖先上都没有对应的 xmlns:dc 声明。几乎每次的起因都是:从一份更大的文档里复制出了片段,而前缀声明在那份文档的根元素上。
解决办法 在片段最外层元素上补上声明。前缀只是一个局部别名,真正标识词汇表的是它绑定的那个 URI,所以请从原文档里原样复制 URI,不要自己编一个。
在构建里失败的文件,在这里却通过了,或者反过来。
成因 编码问题。磁盘上的文件是一串字节,声明告诉解析器该怎么解码,因此一份 UTF-8 文件被声明为 ISO-8859-1 就会出错。而粘贴进本页的文本,浏览器早已完成解码,这里的声明形同参考。
解决办法 用能读字节的工具确认文件真实编码,再让声明与文件实际保存的编码一致。另外留意声明之前的 UTF-8 字节序标记,若干严格解析器会因为它直接报错。
之类的实体被报告为未定义。
成因 XML 只预定义五个实体:&、<、>、" 和 '。其余的(包括整套 HTML 实体)都必须在 DTD 中声明,而浏览器不会抓取外部 DTD。
解决办法 改用数字字符引用,例如不换行空格写成  ;或者在 DOCTYPE 的内部子集里声明该实体。不要指望消费方去抓取外部 DTD,很多解析器默认就把这条路关掉了。
在这里是良构的,接收方系统却依然拒收。
成因 你遇到的是有效性问题而不是语法问题:缺少必需元素、值的类型不对、元素顺序不对,或者元素名都对但挂在了错误的命名空间 URI 下。
解决办法 向对方要来 XSD,或者要来消费方产生的确切错误,然后用 xmllint、语言层面的模式校验库,或厂商自带的校验器按那份模式做校验。本工具是有意停在语法这一层的。
适合用这个工具的场景
- 在去翻模式文件之前,先快速判定被拒收的载荷究竟是语法问题还是内容问题。
- 把模板或字符串拼接产生的标记交给严格解析器之前,先确认它是良构的。
- 确认一份陌生文档的根元素名称和规模,这往往足以辨认出对方发来的是哪种格式。
- 合并冲突留下残余标记之后,给手工改过的配置文件做一次基本体检。
应该换用别的工具的场景
- 按 XSD、DTD 或 RELAX NG 文法做校验。这里没有模式输入口,请使用 xmllint 或平台自带的校验型解析器。
- 重新缩进或压缩文档。那是 XML 格式化与压缩工具的工作,它能正确保留混合内容。
- 检查 HTML。HTML5 并不是 XML 方言,它允许未闭合标签、不加引号的属性和裸露的与号,而这些都会被本解析器拒绝;请改用 HTML 预览工具。
隐私与数据处理
解析在当前浏览器标签页内通过平台的 DOMParser 完成。文档不会上传,关闭标签页后也不留存。DOCTYPE 引用的外部资源不会被抓取,这既是隐私属性也是正确的安全姿态,因为抓取外部实体正是 XXE 攻击的实现机制。XML 配置文件经常在普通属性里明文放着连接串和口令,请先脱敏再粘贴。
相关开发者工具
常见问题
良构的 XML 和有效的 XML 差在哪?
良构描述语法:一个根元素、标签正确嵌套并闭合、属性加引号、保留字符已转义、命名空间前缀已声明。有效描述的是内容相对于所声明模式的符合程度。有效的文档一定良构,反过来不成立;实践中被拒收的文档,大多数都是良构但无效。
校验工具会检查我的 XSD 吗?
不会。它只解析文档并报告良构性。模式校验需要模式文件本身,而本工具不接受模式输入。语法干净之后,请用带 --schema 选项的 xmllint,或者你所用语言里的校验型解析器。
会加载外部实体或外部 DTD 吗?
不会。浏览器解析器不抓取 DOCTYPE 引用的外部资源,因此没有任何内容离开你的机器,文档也无法把你内网里的文件拉进来。服务端的安全解析器也是照这个方式配置的,因为解析外部实体正是 XXE 攻击的基础。
为什么 XML 在意标签大小写,HTML 却不在意?
XML 在设计上就区分大小写,所以 Item 和 item 是两个不同的元素名,混用它们的文档不是良构的。HTML 解析器会把标签名归一化,这也是「HTML 不经 XHTML 转换就不能当 XML 处理」的原因之一。
XML 声明是必须写的吗?
对使用 XML 1.0 的 UTF-8 文档可以省略,因为那本就是默认假设。只要编码不是 UTF-8,或者文档会被写入某个消费方可能要猜编码的文件,就应当写上。
元素数量把哪些东西算进去了?
文档中任意深度的所有元素,包括根元素本身。注释、处理指令、属性和文本节点都不计入。它可以用来快速核对收到的文档规模是否符合预期,这常常能抓出一次被截断的传输。