Vim处理代码文件时,经常遇到那种把整个XML文档压成一行或几行的文件,所有标签和属性挤在一起,可读性非常差。Vim本身没有内置的XML美化命令,但它提供了缓冲区过滤机制,可以把当前文件内容交给外部程序处理后再覆盖回来。围绕这个思路,可以用xmllint、tidy或Python脚本快速完成XML格式化。下面分别介绍这些方法的具体操作、适用场景和常见问题。

借助xmllint一行命令完成格式化
xmllint是libxml2工具集中的命令行程序,大多数Linux发行版可以直接安装,Debian或Ubuntu使用sudo apt install libxml2-utils,CentOS等系统使用sudo yum install libxml2。macOS通常自带xmllint,Windows用户可以从官网下载libxml2的二进制包,或者使用MSYS2、WSL等环境。安装完成后,在终端执行xmllint --version能输出版本号即可。
在Vim中打开XML文件后,执行以下命令::%!xmllint --format -。这里的%表示整个文件,!表示把该范围内的行交给外部命令处理,xmllint --format -中的-告诉xmllint从标准输入读取内容,格式化结果会替换当前缓冲区。如果文件只有部分区域需要格式化,可以先进入可视模式选中目标行,然后按:,Vim会自动填入'<,'>范围,再执行!xmllint --format -即可。这个操作只作用于选中的行,不会影响文件其他部分。
" 格式化整个文件 :%!xmllint --format - " 格式化选中的行范围 :'<,'>!xmllint --format -
默认缩进是两空格,如果希望改成四空格,可以设置环境变量XMLLINT_INDENT,例如在Vim中执行:let $XMLLINT_INDENT=' ',再运行格式化命令。注意冒号后不能有空格。还可以把该命令映射成快捷键,在.vimrc中加入映射,按leader键加fx即可格式化整个文件。但需要注意,xmllint要求XML结构良好,如果存在标签未闭合、属性缺少引号等问题,会直接报错,并返回原始内容不变。此时可以先执行:%!xmllint --recover --format -,不过恢复模式可能改变原始结构,只适合抢救损坏严重的文档。
使用tidy和Python脚本作为备选方案
tidy原本是HTML整理工具,也能处理XML。安装后可以执行:%!tidy -xml -i -w 0,-i表示缩进,-w 0表示不限制行宽,避免长属性被强制折行。与xmllint相比,tidy对属性换行和自闭合标签的处理略有不同。例如xmllint会把空元素写成<item/>,tidy默认也可能输出<item></item>,这取决于具体版本和选项。如果对输出格式有严格约定,需要先测试两者差异。
也可以使用Python脚本。以下脚本使用xml.dom.minidom解析并输出美化结果:
import sys import xml.dom.minidom data = sys.stdin.read() dom = xml.dom.minidom.parseString(data) print(dom.toprettyxml(indent=' '))
保存为format_xml.py后,在Vim中执行:%!python3 format_xml.py。这种方式的优点是跨平台一致,Python几乎是每个开发者的标配,不需要额外安装系统依赖。xml.dom.minidom的缩进控制比较稳定,但会引入额外的换行和空白节点,如果原始XML对空白敏感,需要谨慎使用。
如果XML很大,dom解析会消耗大量内存,此时可以尝试lxml的etree或直接使用xmlstarlet。xmlstarlet是一个专门的XML命令行工具,格式化命令为xmlstarlet fo -s 2,-s 2指定两空格缩进。在Vim中执行:%!xmlstarlet fo -s 2。安装方式为apt install xmlstarlet或yum install xmlstarlet。它对大型XML文件的内存占用通常比Python的DOM方式更低,而且提供更多XPath处理能力,适合需要边格式化边提取数据的场景。
自定义命令与编码、错误排查
如果经常处理XML,可以把命令封装成Vim命令。在.vimrc中加入以下代码:
command! FormatXML %!xmllint --format - nnoremap <leader>fx :FormatXML<CR>
以后打开XML文件,按leader键加fx就会调用xmllint格式化整个缓冲区。如果希望只针对xml文件类型生效,可以添加条件映射:
autocmd FileType xml nnoremap <buffer> <localleader>fx :%!xmllint --format -<CR>
这样只影响XML文件,不会干扰其他文件类型的快捷键。
处理中文XML时要注意编码。xmllint默认按UTF-8识别输入,如果文件是GBK或GB2312编码,直接格式化可能乱码或报错。可以在Vim中先执行:set fileencoding=utf-8,再用iconv转换::%!iconv -f GBK -t UTF-8 | xmllint --format - | iconv -f UTF-8 -t GBK。这条命令先转成UTF-8给xmllint处理,再转回GBK写回缓冲区。管道在Vim过滤命令中可以正常使用。如果XML声明中写了编码,例如<?xml version="1.0" encoding="GBK"?>,格式化后应该保持声明与实际编码一致,否则解析器会报错。
常见错误包括“Namespace prefix not defined”“xmlParseEntityRef no name”等,基本都是XML本身不符合规范或实体引用写错。xmllint会在命令输出中给出错误位置,但不会替换缓冲区。此时可以先用:!xmllint --noout %检查错误,修复后再格式化。如果希望得到更详细的位置信息,使用:!xmllint --format --noout %并查看Quickfix窗口。不同系统的终端输出可能含有颜色控制符,可以在命令前加上TERM=dumb来避免。
总体而言,在Vim中格式化XML最直接的方式就是利用过滤命令和外部工具。xmllint适合大多数标准XML文件,tidy和xmlstarlet提供了更多格式控制选项,而Python脚本则适合需要跨平台一致性的场景。把格式化动作封装成命令或快捷键后,处理XML文件会顺手很多,不用再手动整理缩进。