如何美化XML:属性换行、闭合标签修正及保留注释
可行的XML美化方案
一、自定义Java格式化逻辑(结合DOM解析)
既然现有工具没法同时满足所有需求,自己基于DOM API实现格式化是最可控的方案,能精准适配你的格式要求还保留注释:
- 开启注释保留的XML解析:
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setIgnoringComments(false); // 关键配置:不忽略注释 DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.parse(new File("你的输入XML路径")); - 递归遍历节点生成格式化文本:
核心逻辑分几个关键点:- 注释节点直接按缩进输出,不做修改
- 元素节点判断属性数量:单属性和标签同行,多属性每个单独换行并对齐缩进
- 空内容标签直接输出自闭合形式,替代冗余的
<tag></tag>
- 核心逻辑片段示例:
private void formatNode(Node node, int indent, StringBuilder sb) { // 处理注释节点 if (node instanceof Comment) { sb.append(getIndent(indent)).append("<!--").append(node.getTextContent()).append("-->\n"); return; } if (!(node instanceof Element)) return; Element elem = (Element) node; sb.append(getIndent(indent)).append("<").append(elem.getTagName()); NamedNodeMap attrs = elem.getAttributes(); int attrCount = attrs.getLength(); // 处理属性 if (attrCount > 0) { if (attrCount == 1) { Node attr = attrs.item(0); sb.append(" ").append(attr.getNodeName()).append("=\"").append(attr.getNodeValue()).append("\""); } else { sb.append("\n"); for (int i = 0; i < attrCount; i++) { Node attr = attrs.item(i); sb.append(getIndent(indent + 1)) .append(attr.getNodeName()).append("=\"").append(attr.getNodeValue()).append("\"\n"); } sb.append(getIndent(indent)); } } // 判断是否为空内容标签 boolean isEmpty = elem.getChildNodes().getLength() == 0 || (elem.getChildNodes().getLength() == 1 && elem.getFirstChild() instanceof Text && ((Text)elem.getFirstChild()).getTextContent().trim().isEmpty()); if (isEmpty) { sb.append("/>\n"); } else { sb.append(">\n"); NodeList children = elem.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { formatNode(children.item(i), indent + 1, sb); } sb.append(getIndent(indent)).append("</").append(elem.getTagName()).append(">\n"); } } // 生成缩进字符串,可自定义空格数 private String getIndent(int level) { return " ".repeat(level); }
二、字符串层面正则修正(适合简单场景)
如果不想写复杂的DOM逻辑,正则可以临时解决问题,但注意仅适合结构简单、无特殊字符(比如属性值含</>)的XML:
- 替换空闭合标签为自闭合标签:
// 避开注释内的内容,匹配纯空标签 String regex = "(?<!<!--.*)<([a-zA-Z0-9_-]+)(\\s+[^>]*)?></\\1>(?!.*-->)"; String formattedXml = rawXml.replaceAll(regex, "<$1$2/>"); - 调整属性换行格式:
// 处理单属性同行、多属性换行 formattedXml = formattedXml.replaceAll("<([a-zA-Z0-9_-]+)\\s+([^>]+?)>", m -> { String tag = m.group(1); String attrs = m.group(2); String[] attrArr = attrs.split("\\s+(?=[a-zA-Z0-9_-]+=)"); if (attrArr.length == 1) { return "<" + tag + " " + attrs + ">"; } else { StringBuilder sb = new StringBuilder("<" + tag + "\n"); for (String attr : attrArr) { sb.append(" ").append(attr).append("\n"); } sb.append(">"); return sb.toString(); } });
三、第三方库:Jackson XML(需自定义序列化)
Jackson XML可以通过配置和自定义序列化器实现需求,稳定性更高:
- 添加依赖:
<dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> <version>2.15.2</version> </dependency> - 基础配置+自定义序列化:
XmlMapper mapper = new XmlMapper(); mapper.enable(SerializationFeature.INDENT_OUTPUT); // 开启空标签自闭合 mapper.configure(ToXmlGenerator.Feature.WRITE_XML_EMPTY_ELEMENTS_AS_EMPTY_TAGS, false); // 要实现单属性同行、多属性换行,需要自定义XmlSerializer扩展类,重写属性序列化逻辑
这个方案需要额外写自定义序列化器,但胜在能处理复杂XML场景,且维护性强。
内容的提问来源于stack exchange,提问作者TenderDen
相关产品推荐
相关产品推荐

