使用方法
XML格式化一共三步,打开页面时输入框里已经有一段可直接运行的示例,不用自己先造数据。
- 把挤成一行的 XML 贴进上面的输入框。示例是一份 260 字节、2 行的图书目录,根节点
catalog下挂 2 个book。 - 选缩进档位:2 空格(默认)、4 空格或 Tab;再决定要不要开「属性满 3 个换行」和「保留注释」。示例默认两项都开。
- 点「格式化」,或者在文本框里按 Ctrl+Enter;结果区给出排版后的文本,下方统计条显示元素节点数、属性数与最大层级深度。
- 点「复制结果」把排版后的文本带走。如果解析失败,红色提示会带上出错的行号和列号,改完再点一次即可。
这个工具能解决什么
接口联调时最常见的情形是对方把整个配置文件压成一行发过来,200 多个字符挤在一行里,想看某个节点的值得横向拖半天。排版后层级一眼可见,哪个 book 少了一个 price 立刻能看出来。
第二种情形是定位不合法的 XML。XML 不允许裸 &、不允许标签不闭合、不允许根元素后面还有内容,但这些错误在服务端的报错往往只有一句 "not well-formed"。本页用浏览器原生解析器给出行列号,配合下面的报错对照表可以直接定位到字符。
第三种情形是准备提交进版本库的配置。缩进一致、属性排布一致,diff 才不会每次提交都全篇飘红。把团队统一的档位记下来,提交前跑一遍即可。
本页只做缩进重排与合法性定位,不做结构校验、不做 DTD 或 XSD 校验、不做数据格式转换。要把 XML 变成 JSON 请改用 XML转JSON转换器,反过来用 JSON转XML转换器;语法层面的合法性判定请走 JSON校验工具 那类专门页面,把体积压到最小则要看 JSON压缩工具 的思路。
原理和变量
解析用的是哪个函数。new DOMParser().parseFromString(src, 'application/xml')。第二个参数必须是 application/xml 而不是 text/html:后者走 HTML 容错解析,会自动补全缺失的结束标签、把未知标签一律小写化,一段根本不合法的 XML 也能"解析成功",等于把错误藏起来。严格模式下不合法就会生成 <parsererror> 节点,本页靠它取行列号。
序列化为什么不用 XMLSerializer。XMLSerializer 按规范只做"把 DOM 写回字符串",它不插入任何缩进空白,输入是一行输出还是一行。所以重排必须自己走:本页写了一个 walk(node, depth),按 nodeType 分支——1 是元素、3 是文本、4 是 CDATA、8 是注释、7 是处理指令——每层把缩进字符串重复 depth 次,最后用数组 join('') 拼成整串,而不是用 += 反复追加(后者在几万节点时会退化成 O(n²) 的字符串拷贝)。
缩进档位为什么默认 2 空格。同一份三层结构,2 空格 59 字节、4 空格 77 字节、Tab 50 字节。2 空格在嵌套深的配置里不容易撞到右边界,横向滚动最少;4 空格是 Java 生态(Ant、Maven、AndroidManifest)的惯例,换到那里更合群;Tab 最省字节,但各编辑器显示宽度从 2 到 8 不等,进版本库后很容易被别人顺手改成空格,制造无意义 diff。属性换行的阈值取 3 个:1 到 2 个属性同行更紧凑,3 个起换行才划算。
遵循的标准。合法性判定按 W3C XML 1.0(Fifth Edition)的 well-formed 约束;DOMParser 与序列化接口属于 W3C DOM Parsing and Serialization 规范;实体转义严格按 XML 1.0 第 4.6 节,只有 & < > " ' 这 5 个预定义实体,其余字符一律写成数字字符引用,比如不换行空格要写   而不是 HTML 里的 。
输入怎么变成输出。先去掉开头的 BOM(U+FEFF,UTF-8 下是 EF BB BF 三字节)→ 交给 DOMParser → 检查 parsererror → 有则报错,无则从 documentElement 开始递归;遇到元素先看它的子节点是不是全为文本或 CDATA,是就写成单行内联 <title>…</title>,否则每个子节点换行并缩进一层;没有子节点的元素写成自闭合 <tag />;文本节点只转义 & < >,属性值额外转义 "。
常见误用的正确解释。输入里有一个裸 & 就报错,这不是工具挑刺——XML 1.0 规定 & 只能作为实体引用的开头,写成本页能过的形式是 &。同理,注释里出现连续两个连字符 -- 会报错,这是 XML 1.0 对注释的硬约束,不是解析器的实现缺陷。
完整示例(数值仅作演示)
输入框里的默认示例,压缩态是 2 行 260 字节,按 2 空格缩进、属性满 3 个换行排版后变成 15 行 325 字节,体积增加 25%:
<?xml version="1.0" encoding="UTF-8"?>
<catalog>
<book id="1"
lang="zh"
stock="12">
<title>XML 入门</title>
<price currency="CNY">39.00</price>
</book>
<book id="2"
lang="zh"
stock="12">
<title>深入 XML</title>
<price currency="CNY">58.00</price>
</book>
</catalog>
统计条会给出:元素节点 7 个(catalog 1 + book 2 + title 2 + price 2),属性 8 个(每个 book 3 个共 6,每个 price 1 个共 2),最大层级 3 层。book 有 3 个属性所以换行排布,price 只有 1 个属性仍留在同一行——这就是阈值取 3 的直接体现。
输入范围与对照表
下面是 DOMParser 在严格模式下最常抛的 16 类报错,默认显示 12 行,点「加载更多」看全部,也可以导出 CSV 带在排查文档里。
| 解析器报错关键词 | 真实原因 | 怎么改 |
|---|---|---|
Opening and ending tag mismatch | 开始标签与结束标签名字不一致,或顺序交叉嵌套 | 按报错行列把结束标签改成同名,注意嵌套不能交叉 |
Extra content at the end of the document | 根元素闭合之后还有内容 | XML 只允许一个根元素,多余内容移到根元素内部 |
xmlParseEntityRef: no name | 文本里出现了没有名字的裸 & | 写成 &;XML 只认 5 个预定义实体 |
Unbound prefix | 标签或属性用了带冒号的前缀但没声明命名空间 | 在根元素补 xmlns:前缀="URI" 声明 |
XML declaration allowed only at the start | 声明之前有多余空白、换行或 BOM | 删掉 <?xml 之前的所有字符,含 UTF-8 BOM |
Specification mandate value for attribute | 属性只写了名字没写值,或值没加引号 | XML 属性值必须写成 name="value" |
Attribute name redefined | 同一个元素里属性名重复出现 | XML 不允许重复属性,删掉其中一个 |
Premature end of data | 标签还没闭合就到了文本末尾 | 补上结束标签,或改成自闭合形式 |
Comment must not contain '--' | 注释内部出现了连续两个连字符 | 注释里不要写 --,改用单个 - 或文字 |
Sequence ']]>' not allowed in content | 普通文本里出现 CDATA 的结束标记 | 写成 ]]>,或把整段包进 CDATA |
Invalid character in tag name | 标签名里有空格、引号或非法起始字符 | 标签名不能以数字、标点或 xml 开头 |
Entity 'nbsp' not defined | 沿用了 HTML 的实体名 | XML 只认 5 个,其余写数字引用   |
Blank needed after '<' | 尖括号后面没跟合法标签名 | 检查是不是漏写标签名或多打了一个 < |
Input is not proper UTF-8 | encoding 声明与实际字节编码不一致 | 把文件另存为 UTF-8,或改声明里的 encoding |
Namespace prefix is not defined | 属性上的前缀未声明(元素上已声明不算) | 属性前缀也要有对应的 xmlns 声明 |
Attribute is not allowed | 触发了 DTD 约束 | 本页只判定 well-formed,DTD 校验需另找专门工具 |
边界与精度
体积阈值。输入超过 2 MB 直接拒绝,不尝试解析;超过 200 KB(约 195 KB 的显示值)会先提示一次再继续,因为一次递归遍历几 MB 的文本会明显卡住标签页。节点数超过 50000 也拒绝,这个量级对应大约 1.4 MB 的文档,已经超出手工阅读的范围。
嵌套深度上限 200。递归实现在极深嵌套上会爆调用栈,200 层对真实配置文档完全够用(Spring 配置、AndroidManifest 一般不超过 10 层),超了就直接给出提示而不是让页面崩掉。
空白怎么处理。元素之间用于缩进的空白文本节点会被丢弃,重新按层级生成;但纯文本内容里的空格原样保留,不做 trim。所以 <title> XML 入门 </title> 排版后仍是带前后空格的 XML 入门——如果原文本身就把值写成跨行缩进的形式,换行和缩进会被当成值的一部分保留下来,这是 XML 的语义,不是排版 bug。
注释可关。「保留注释」取消勾选后,注释节点会被跳过,输出里不再出现。默认勾选,因为配置文件里的注释往往记录着为什么这么配,丢掉代价很大。
不做的两件事。一是不做 DTD 与 XSD 校验:本页只判定 well-formed(结构合法),"合法但不符合业务 schema" 检测不出来。二是不做编码转换:输入按 UTF-8 处理,如果原始文件是 GB2312,需要先转码再贴进来,否则会报 "Input is not proper UTF-8"。
常见问题
为什么提示里只有一个英文报错,不给我改成中文?
报错原文来自浏览器内置的 libxml2,不同浏览器措辞不完全一致。本页把行列号和原文一起附上,并额外匹配了几类高频错误补一句中文解释;匹配不上时保留原文,避免为了翻译而误判原因。
排版后体积变大了,正常吗?
正常。示例从 260 字节涨到 325 字节,增加 25%。缩进空白是纯增量,4 空格档位比 2 空格涨得更多。要压回去得走去掉所有节点间空白的路子,那是另一类工具做的事,本页只负责排版。
XML 声明怎么没了?
不会丢。声明属于处理指令节点,会被原样输出在第一行。如果它消失了,说明原文本里声明之前还有字符,解析器根本没把它当声明,会报 "XML declaration allowed only at the start"。
带 xmlns 的文档,为什么不能删掉 xmlns?
xmlns 是命名空间声明,不是普通属性。删掉之后带前缀的标签会立刻变成 Unbound prefix。本页把它当成普通属性输出,所以顺序和原文一致,但请不要在结果里手动删除它。
CDATA 里的内容会被转义吗?
不会。CDATA 段按 nodeType 4 单独处理,原样输出,内部的尖括号和 & 都不转义。这正是 CDATA 存在的意义——如果你发现 CDATA 内容被转义了,说明那段文本根本没被识别成 CDATA,检查开始标记是否写全。
为什么我不写 <?xml …?> 也能格式化?
XML 声明是可选的,只有 encoding 不是 UTF-8 或 UTF-16 时才必须写。不写时解析器按 UTF-8 处理,本页同样处理。
属性值里的换行会保留吗?
会,并且会被规范化。按 XML 1.0 的属性值规范化规则,换行与制表符在属性值里会被转成空格,这是规范行为而不是本页的处理。需要保留换行就不要放进属性,改用子元素。
粘贴进去后什么都不出,也没报错?
先确认输入框不是空的:本页对空输入会明确提示"待排版的 XML 不能为空",不会静默输出空串。如果输入非空却没输出,说明尺寸或节点数超过了上面写的阈值,提示会写在红色区域里。
相关工具
- XML转JSON转换器:把 XML 的结构转成 JSON,属性会变成带前缀的键。
- JSON转XML转换器:反向转换,需要指定根元素名。
- XML站点地图生成器:按 sitemap 协议生成站点地图 XML。
- JSON校验工具:语法层面的合法性判定,报错同样给出行列位置。
- JSON压缩工具:把排版后的文本压回一行,与本页方向相反。
- HTMLEntity速查表:HTML 实体与字符对照,注意它和 XML 的 5 个预定义实体不是一套。
- 字符串长度统计:排版前后对比字节数与字符数。