如何实现XML同层级同名标签的识别与序号式重命名?
问题描述
项目需要实现XML转换逻辑:对同一父节点下的同名标签按出现顺序追加序号重命名。直接调用document.renameNode()未达到预期效果,核心卡点是无法准确识别同层级下需要重命名的同名目标标签。
示例输入XML
<A> <B> <C></C> </B> <B> <D> <E></E> </D> </B> <B> <F></F> </B> </A>
期望输出XML
<A> <B_1> <C></C> </B_1> <B_2> <D> <E></E> </D> </B_2> <B_3> <F></F> </B_3> </A>
实现方案
直接全局匹配标签名调用重命名方法会把不同层级的同名标签误统计,必须按层级独立处理:从根节点开始深度优先遍历,每到一个父节点,只统计它的直接子节点的标签重复情况,再按顺序重命名,最后递归处理下一层级即可。
具体执行步骤
- 从根节点开始递归遍历,保证每一层的计数逻辑完全独立,不跨层干扰
- 处理单个父节点时,第一遍遍历所有直接子节点,用哈希表记录每个标签名在当前层的总出现次数
- 第二遍遍历该父节点的直接子节点,维护每个标签名的当前序号计数器,若该标签在当前层总出现次数大于1,就将节点重命名为
原标签名_当前序号,序号每遇到一个同名标签递增1 - 对每个处理完的子节点,递归执行上述逻辑,处理它自身的子节点层级
Java(DOM解析)可运行示例
import org.w3c.dom.*; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import java.io.StringReader; import java.io.StringWriter; import java.util.HashMap; import java.util.Map; public class XmlSameLevelTagRenamer { public static String rename(String xmlInput) throws Exception { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.parse(new org.xml.sax.InputSource(new StringReader(xmlInput))); // 从根节点启动递归处理 processChildLevel(doc.getDocumentElement()); // 输出转换结果 Transformer transformer = TransformerFactory.newInstance().newTransformer(); transformer.setOutputProperty("omit-xml-declaration", "yes"); StringWriter writer = new StringWriter(); transformer.transform(new DOMSource(doc), new StreamResult(writer)); return writer.getBuffer().toString(); } private static void processChildLevel(Element parent) { NodeList childNodes = parent.getChildNodes(); Map<String, Integer> totalCountMap = new HashMap<>(); // 第一遍:统计当前层所有标签的出现总次数 for (int i = 0; i < childNodes.getLength(); i++) { Node node = childNodes.item(i); if (node.getNodeType() == Node.ELEMENT_NODE) { String tag = node.getNodeName(); totalCountMap.put(tag, totalCountMap.getOrDefault(tag, 0) + 1); } } Map<String, Integer> currentIndexMap = new HashMap<>(); // 第二遍:重命名+递归处理下一层 for (int i = 0; i < childNodes.getLength(); i++) { Node node = childNodes.item(i); if (node.getNodeType() == Node.ELEMENT_NODE) { String tag = node.getNodeName(); // 仅当前层存在重名标签时才追加序号 if (totalCountMap.get(tag) > 1) { int currentIdx = currentIndexMap.getOrDefault(tag, 0) + 1; currentIndexMap.put(tag, currentIdx); String newTagName = tag + "_" + currentIdx; node.getOwnerDocument().renameNode(node, node.getNamespaceURI(), newTagName); } processChildLevel((Element) node); } } } }
避坑说明
- 禁止跨层级统计标签名,必须以直接父节点为单位隔离计数,否则会把不同层级的同名标签错误追加序号
- 必须分两次遍历子节点:第一次统计总重复次数,第二次再执行重命名,避免遍历到第一个同名标签时无法判断后续是否有同名节点,导致不需要加序号的标签被错误修改
- 处理带命名空间的XML时,
renameNode方法要传入原节点的命名空间URI,避免命名空间属性丢失
内容的提问来源于stack exchange,提问作者ZeeCos
相关产品推荐
相关产品推荐

