XML文件XPATH属性/元素及值差异对比:输出结果优化诉求
优化XML对比逻辑以输出新增节点的完整XPATH及值
问题背景
需对比F6.XML与F7.XML在XPATH属性、元素及值上的差异,当前使用XMLUnit的DiffBuilder输出中,新增节点仅提示父节点信息,无法获取新增子节点的完整XPATH与对应值,需优化逻辑以输出符合要求的格式。
待对比XML文件
F6.XML
<MetaData xmlns="urn:wco:datamodel:WCO:DocumentMetaData-DMS:2"> <Declaration xmlns="urn:wco:datamodel:WCO:DEC-DMS:2"> <FunctionCode>9</FunctionCode> <FunctionalReferenceID>432189615698-46239</FunctionalReferenceID> <TypeCode>EXD</TypeCode> <GoodsItemQuantity>1</GoodsItemQuantity> <InvoiceAmount currencyID="GBP">7001.00</InvoiceAmount> </Declaration> </MetaData>
F7.XML
<MetaData xmlns="urn:wco:datamodel:WCO:DocumentMetaData-DMS:2"> <Declaration xmlns="urn:wco:datamodel:WCO:DEC-DMS:2"> <FunctionCode>9</FunctionCode> <AdditionalInformation> <StatementCode>AG001</StatementCode> <StatementDescription>AG001</StatementDescription> <Pointer> <SequenceNumeric>1</SequenceNumeric> <DocumentSectionCode>42A</DocumentSectionCode> </Pointer> </AdditionalInformation> <FunctionalReferenceID>432189615698-46239</FunctionalReferenceID> <GoodsItemQuantity>11</GoodsItemQuantity> <InvoiceAmount currencyID="GBP">7001.00</InvoiceAmount> </Declaration> </MetaData>
原对比代码
Diff diffs = DiffBuilder.compare(xml1).ignoreWhitespace().ignoreWhitespace(). withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byNameAndAllAttributes)) . withTest(xml2).build(); Iterator<Difference> iterator = diffs.getDifferences().iterator(); while (iterator.hasNext()) { Difference nextDiff = iterator.next(); System.out.println("-------------------->"+nextDiff); }
原输出结果
-------------------->Expected child nodelist sequence '1' but was '2' - comparing <FunctionalReferenceID...> at /MetaData[1]/Declaration[1]/FunctionalReferenceID[1] to <FunctionalReferenceID...> at /MetaData[1]/Declaration[1]/FunctionalReferenceID[1] (SIMILAR) -------------------->Expected text value '1' but was '11' - comparing <GoodsItemQuantity ...>1</GoodsItemQuantity> at /MetaData[1]/Declaration[1]/GoodsItemQuantity[1]/text()[1] to <GoodsItemQuantity ...>11</GoodsItemQuantity> at /MetaData[1]/Declaration[1]/GoodsItemQuantity[1]/text()[1] (DIFFERENT) -------------------->Expected child '{urn:wco:datamodel:WCO:DEC-DMS:2}TypeCode' but was 'null' - comparing <TypeCode...> at /MetaData[1]/Declaration[1]/TypeCode[1] to <NULL> (DIFFERENT) -------------------->Expected child 'null' but was '{urn:wco:datamodel:WCO:DEC-DMS:2}AdditionalInformation' - comparing <NULL> to <AdditionalInformation...> at /MetaData[1]/Declaration[1]/AdditionalInformation[1] (DIFFERENT)
期望输出格式
//MetaData[1]/Declaration[1]/AdditionalInformation[1]/StatementCode[1]='AG001' //MetaData[1]/Declaration[1]/AdditionalInformation[1]/StatementDescription[1]='AG001' //MetaData[1]/Declaration[1]/AdditionalInformation[1]/Pointer[1]/SequenceNumeric[1]='1' //MetaData[1]/Declaration[1]/AdditionalInformation[1]/Pointer[1]/DocumentSectionCode[1]='42A'
优化后的对比逻辑
针对新增节点,递归遍历其下所有文本节点并生成完整XPATH与对应值,同时保留原有修改、删除节点的输出格式。优化后的代码如下:
import org.xmlunit.diff.*; import org.xmlunit.xpath.JAXPXPathEngine; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import java.io.StringReader; public class XmlDiffOptimized { public static void main(String[] args) throws Exception { String xml1 = "<MetaData xmlns=\"urn:wco:datamodel:WCO:DocumentMetaData-DMS:2\">\n" + " <Declaration xmlns=\"urn:wco:datamodel:WCO:DEC-DMS:2\">\n" + " <FunctionCode>9</FunctionCode>\n" + " <FunctionalReferenceID>432189615698-46239</FunctionalReferenceID>\n" + " <TypeCode>EXD</TypeCode>\n" + " <GoodsItemQuantity>1</GoodsItemQuantity>\n" + " <InvoiceAmount currencyID=\"GBP\">7001.00</InvoiceAmount>\n" + " </Declaration>\n" + "</MetaData>"; String xml2 = "<MetaData xmlns=\"urn:wco:datamodel:WCO:DocumentMetaData-DMS:2\">\n" + " <Declaration xmlns=\"urn:wco:datamodel:WCO:DEC-DMS:2\">\n" + " <FunctionCode>9</FunctionCode>\n" + " <AdditionalInformation>\n" + " <StatementCode>AG001</StatementCode>\n" + " <StatementDescription>AG001</StatementDescription>\n" + " <Pointer>\n" + " <SequenceNumeric>1</SequenceNumeric>\n" + " <DocumentSectionCode>42A</DocumentSectionCode>\n" + " </Pointer>\n" + " </AdditionalInformation>\n" + " <FunctionalReferenceID>432189615698-46239</FunctionalReferenceID>\n" + " <GoodsItemQuantity>11</GoodsItemQuantity>\n" + " <InvoiceAmount currencyID=\"GBP\">7001.00</InvoiceAmount>\n" + " </Declaration>\n" + "</MetaData>"; // 构建XML对比实例 Diff diffs = DiffBuilder.compare(xml1) .ignoreWhitespace() .withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byNameAndAllAttributes)) .withTest(xml2) .build(); // 解析测试XML用于遍历新增节点子节点 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); DocumentBuilder builder = factory.newDocumentBuilder(); Node testDoc = builder.parse(new StringReader(xml2)); JAXPXPathEngine xPathEngine = new JAXPXPathEngine(); xPathEngine.setNamespaceContext(diffs.getNamespaceContext()); Iterator<Difference> iterator = diffs.getDifferences().iterator(); while (iterator.hasNext()) { Difference nextDiff = iterator.next(); Difference.Type diffType = nextDiff.getType(); // 处理新增节点:输出所有子文本节点的完整XPATH与值 if (diffType == Difference.Type.NODE_ADDED) { String addedNodeXPath = nextDiff.getTestDetails().getXPath(); NodeList textNodes = xPathEngine.selectNodes(addedNodeXPath + "//text()[normalize-space()]", testDoc); for (int i = 0; i < textNodes.getLength(); i++) { Node textNode = textNodes.item(i); Node parentElement = textNode.getParentNode(); String elementXPath = xPathEngine.getXPath(parentElement); // 转换为要求的//开头格式 String formattedXPath = elementXPath.replaceFirst("^/", "//"); String textValue = textNode.getTextContent().trim(); System.out.printf("%s='%s'%n", formattedXPath, textValue); } } else { // 保留原有修改、删除、顺序差异的输出 System.out.println("-------------------->" + nextDiff); } } } }
优化说明
- 新增节点处理:检测到
NODE_ADDED类型差异时,通过XPATH查询新增节点下所有非空文本节点,遍历生成父元素的完整XPATH。 - 格式转换:将XMLUnit默认的绝对路径转换为需求要求的
//开头格式。 - 兼容原有输出:对修改、删除、顺序类差异,保留原输出格式,满足需求中对这类结果的接受要求。
内容的提问来源于stack exchange,提问作者Sreepad Chitragar
相关产品推荐
相关产品推荐

