XML格式化

按层级缩进重排 XML,属性满三个自动换行,注释与 CDATA 原样保留;解析走浏览器原生 DOMParser 严格模式,出错时指出行列。

—

在文本框里按 Ctrl+Enter(Mac 用 Cmd+Enter)即可重新排版;Enter 留给换行,不会触发。

使用方法

XML格式化一共三步,打开页面时输入框里已经有一段可直接运行的示例,不用自己先造数据。

  1. 把挤成一行的 XML 贴进上面的输入框。示例是一份 260 字节、2 行的图书目录,根节点 catalog 下挂 2 个 book。
  2. 选缩进档位:2 空格(默认)、4 空格或 Tab;再决定要不要开「属性满 3 个换行」和「保留注释」。示例默认两项都开。
  3. 点「格式化」,或者在文本框里按 Ctrl+Enter;结果区给出排版后的文本,下方统计条显示元素节点数、属性数与最大层级深度。
  4. 点「复制结果」把排版后的文本带走。如果解析失败,红色提示会带上出错的行号和列号,改完再点一次即可。

这个工具能解决什么

接口联调时最常见的情形是对方把整个配置文件压成一行发过来,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 节,只有 &amp; &lt; &gt; &quot; &apos; 这 5 个预定义实体,其余字符一律写成数字字符引用,比如不换行空格要写 &#160; 而不是 HTML 里的 &nbsp;。

输入怎么变成输出。先去掉开头的 BOM(U+FEFF,UTF-8 下是 EF BB BF 三字节)→ 交给 DOMParser → 检查 parsererror → 有则报错,无则从 documentElement 开始递归;遇到元素先看它的子节点是不是全为文本或 CDATA,是就写成单行内联 <title>…</title>,否则每个子节点换行并缩进一层;没有子节点的元素写成自闭合 <tag />;文本节点只转义 & < >,属性值额外转义 "。

常见误用的正确解释。输入里有一个裸 & 就报错,这不是工具挑刺——XML 1.0 规定 & 只能作为实体引用的开头,写成本页能过的形式是 &amp;。同理,注释里出现连续两个连字符 -- 会报错,这是 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文本里出现了没有名字的裸 &写成 &amp;;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 的结束标记写成 ]]&gt;,或把整段包进 CDATA
Invalid character in tag name标签名里有空格、引号或非法起始字符标签名不能以数字、标点或 xml 开头
Entity 'nbsp' not defined沿用了 HTML 的实体名XML 只认 5 个,其余写数字引用 &#160;
Blank needed after '<'尖括号后面没跟合法标签名检查是不是漏写标签名或多打了一个 <
Input is not proper UTF-8encoding 声明与实际字节编码不一致把文件另存为 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 不能为空",不会静默输出空串。如果输入非空却没输出,说明尺寸或节点数超过了上面写的阈值,提示会写在红色区域里。

相关工具