You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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);
            }
        }
    }
}

优化说明

  1. 新增节点处理:检测到NODE_ADDED类型差异时,通过XPATH查询新增节点下所有非空文本节点,遍历生成父元素的完整XPATH。
  2. 格式转换:将XMLUnit默认的绝对路径转换为需求要求的//开头格式。
  3. 兼容原有输出:对修改、删除、顺序类差异,保留原输出格式,满足需求中对这类结果的接受要求。

内容的提问来源于stack exchange,提问作者Sreepad Chitragar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 02:14:54