3.1 良构XML:第一道质检


3.1 良构 XML:第一道质检

良构(well-formed)是 XML 解析器对文档语法合规性的判定:满足第 1 章全部语法规则——单一根、标签闭合、嵌套不交叉、属性引号、实体转义、声明位置正确。不满足,任何 XML 工具都不会处理这份文档,一步都过不去。本节把良构检查项整理成清单,并训练"看报错定位病灶"的能力。

良构检查项清单

解析器开工前逐项核对的正是这些:

检查项 典型病灶 报错关键词
有且仅有一个根元素 顶层并列两个元素 content not allowed in prolog / junk
标签全部闭合 结束标签漏斜杠 expected ... to close
嵌套不交叉 p 内开 b、p 外关 b mismatched tag
大小写一致 <Stock></stock> mismatched tag
属性值加引号 id=1001 attribute value not quoted
特殊字符转义 &、裸 < not well-formed 实体相关
声明位置 声明前有空行或 BOM XML declaration allowed only at start

值得强调的是"没有容错模式"。HTML 浏览器有悠久的纠错传统——标签没关会猜,属性没引号会补。XML 从设计上拒绝了这条路:宁可拒收,不可猜错。做数据交换时这是优点(契约严格);从 HTML 转来的开发者常觉得苛刻,但正是这份苛刻保证了任何解析器对同一份文档建出同一棵树。

这份严格性有明确的工程回报。想象一条流水线上有五个系统依次处理同一份报文:若解析器可以各自"猜",五棵树可能五种样子,每个系统都要为自己那棵怪树写补丁代码;解析器统一拒绝猜测后,五棵树必然同构,所有系统共享同一套处理逻辑。确定性的树换来的是整个生态的兼容性,这是 XML 设计中最值得体会的一笔交易。也正因此,良构检查不是"可选的规范洁癖",而是文档进入机器世界的最低通行证——过不了这一关,后面的有效校验、转换、查询统统无从谈起。

报错解读训练:三个真实报错

训练一

报错:The matching tag for <item> was not found (line 7) 文档片段: 3 <line sku="A"> 4 <name>螺栓</name> 5 <qty>200<Qty> 6 </line>

报错在 line 7 才喊出来,病灶在 line 5——<Qty> 少了斜杠,解析器把 Qty> 又当开始标签吞进去,一路错位到 line 7 才对不上。教训:报错行号常是"发现处"而非"病灶处",往前找未闭合标签

训练二

报错:Content is not allowed in prolog

prolog 指声明之前的位置。九成原因是文档开头混进了不可见字符——从网页复制带回的全角空格、编辑器加的 BOM、或声明前手滑敲了个回车。排查方法是用能显示不可见字符的编辑器看第一行,或按十六进制检查文件头三字节。

训练三

报错:The entity name must immediately follow the & in the entity reference

内容里有裸 &。常见于把带查询参数的 URL 直接塞进 XML——与符号加参数名会被当成实体开头。修法是 &amp;

<link>http://a.example.org/x?u=1&amp;p=2</link> <!-- 解析后得到的真实值中 &amp; 还原为与符号,转义只存在于文本形态 -->

URL 里的查询参数分隔符是裸与符号的高发区,凡把链接写进 XML 的场合都要过一遍这道手续。同理还有正文中引用商标名带与符号、数学不等式,都属此类。

良构 ≠ 合格:一个提前预告

一份文档可以完美通过良构检查,同时是业务垃圾:

<?xml version="1.0" encoding="UTF-8"?> <order> <id>O-1001</id> <qty>二百件</qty> <id>O-1002</id> </order>

语法挑不出毛病:标签全闭合、嵌套正确。但 qty 该是数字、id 不该出现两次——这些要求写在语法之外,写在契约里。这就是第二道质检"有效"的领地,3.2 节用一份事故文档展开。

💡 排错顺序论:先修良构(语法级),良构过了再修有效(契约级)。两级错误混着修,改一处报一处新错,永远在追着解析器跑。

常见问题与自查习惯

问:为什么同样的文档在 A 工具能打开、B 工具报错? 先怀疑两件事:一是 B 工具校验更严格(比如开启了校验模式而 A 只是宽松浏览);二是编码处理不同——A 恰好按文档实际编码打开,B 按声明或默认编码打开。把两边的报错原文与文件头字节摆在一起对比,答案通常自己浮出来。

问:良构错误会"部分成功"吗? 不会。XML 解析是原子判定:要么整份文档建树成功,要么报错且不提供任何树。这与 HTML 的增量渲染截然不同,也意味着不能指望"先把能读的部分读出来"——上游发来坏文档时,唯一正确动作是拒收并回报错误,而不是带病解析。

日常自查建议养成三个习惯。其一,编辑器开着实时校验写文档,错误在产生的那一刻就消灭。其二,提交前跑一遍命令行良构检查,把"编辑器没报"的侥幸堵死(不同解析器严格度有差异)。其三,接手陌生文档先跑良构再干活——上一手留下的坑,越早发现成本越低。这三个习惯的成本加起来不到一分钟,却能挡掉大部分半夜被叫起来排查的灾难。

本节要点回顾

  • 良构是语法判定,解析器独家执行,不通过整份拒收;
  • 没有容错:任何解析器对同一文档建同一棵树,这是 XML 的设计承诺;
  • 报错行号是发现处,未闭合类错误要向前回溯;
  • prolog 报错查文件头:BOM、不可见字符、声明前空行;
  • URL 中的 & 必须 &amp;,解析后自动还原。

最后补一个"工具间不一致"的辨析。同一份文档,浏览器里显示正常、xmllint 却报错,初学者常怀疑工具坏了。多数情况的解释是:浏览器只把 XML 当展示对象,宽容度高;命令行工具按规范严格判定。另一个方向的不一致也存在——某些编辑器默认把 --recover 之类的修复选项打开,静默纠正了小错误,让你以为文档本来就没问题。结论是把最严格的那个工具当裁判:编辑器的绿灯只能算参考,命令行良构检查通过才算数。CI 里固定用同一个严格工具,本地与线上判定一致,"我这能跑你那不能"的扯皮就从根上消失。

良构这关过了,文档才拿到"接受进一步审查"的资格。下一节看看只过第一关、栽在第二关的真实事故长什么样。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U