如何比较含嵌套内容的XML?XMLUnit使用问题求助
XML比较问题排查与修复
需求说明
需要比较特定结构的XML:
<name>为数组节点,需匹配其所有嵌套字段<TOKEN>节点需忽略<generateDate>,且内部<foo>数组按名称+value属性匹配
待比较XML结构
<ROOT> <name> <name>string</name> <type>number</type> <server>string</server> <service>number</service> <user/> <enabled>boolean</enabled> </name> <name> <name>string</name> <type>number</type> <server>string</server> <service>number</service> <user/> <enabled>boolean</enabled> </name> ... <TOKEN> <generateDate>date</generateDate> <username>string</username> <role>string</role> <type value="string" anotherValue="string"/> <attr value="string"/> <foo value="string1"/> <foo value="string2"/> ... </TOKEN> </ROOT>
现有问题代码
使用xmlunit-core 2.9.0编写的比较代码,但<name>节点内容比较失败:
Diff diff = DiffBuilder.compare(expected) .withTest(actual) .ignoreWhitespace() .ignoreComments() .checkForSimilar() .withNodeMatcher( new DefaultNodeMatcher( ElementSelectors.conditionalBuilder() .whenElementIsNamed("name") .thenUse(ElementSelectors.and( ElementSelectors.byNameAndAllAttributes, ElementSelectors.byNameAndText )) .whenElementIsNamed("foo") .thenUse(ElementSelectors.byNameAndAttributes("value")) .elseUse(ElementSelectors.byNameAndText) .build() ) ) .withDifferenceEvaluator((comparison, outcome) -> { Node targetNode = comparison.getControlDetails().getTarget(); String nodeName = ""; if (targetNode != null) { nodeName = targetNode.getNodeName(); if ("#text".equals(nodeName) && targetNode.getParentNode() != null) { nodeName = targetNode.getParentNode().getNodeName(); } } if (nodeName.equals("generateDate")) { return ComparisonResult.EQUAL; } return outcome; }) .build();
问题原因与修复方案
1. <name>节点匹配逻辑错误
当前对顶层<name>节点使用ElementSelectors.and(byNameAndAllAttributes, byNameAndText),但顶层<name>本身无属性和直接文本(文本都在子节点中),导致匹配逻辑完全失效。
正确做法是递归匹配<name>的所有子节点内容,确保嵌套字段完全一致:
.whenElementIsNamed("name") .thenUse(ElementSelectors.byRecursiveXPath("./*", ElementSelectors.byNameAndText))
2. 兜底匹配规则冲突
elseUse(ElementSelectors.byNameAndText)会对空节点(如<user/>)生效,这类节点无文本,会触发不必要的差异。需调整兜底规则,兼容空节点:
.elseUse(ElementSelectors.conditional() .when(ElementSelectors.hasText()) .thenUse(ElementSelectors.byNameAndText) .elseUse(ElementSelectors.byName))
3. <generateDate>忽略逻辑优化
原代码仅通过control侧节点判断名称,存在test侧节点为null的情况,需同时兼容两侧节点的判断:
.withDifferenceEvaluator((comparison, outcome) -> { String nodeName = null; Node controlNode = comparison.getControlDetails().getTarget(); Node testNode = comparison.getTestDetails().getTarget(); if (controlNode != null) { nodeName = controlNode.getNodeName(); } else if (testNode != null) { nodeName = testNode.getNodeName(); } if ("generateDate".equals(nodeName)) { return ComparisonResult.EQUAL; } return outcome; })
修复后的完整代码
Diff diff = DiffBuilder.compare(expected) .withTest(actual) .ignoreWhitespace() .ignoreComments() .checkForSimilar() .withNodeMatcher( new DefaultNodeMatcher( ElementSelectors.conditionalBuilder() // 匹配顶层<name>节点:递归比较所有子节点内容 .whenElementIsNamed("name") .thenUse(ElementSelectors.byRecursiveXPath("./*", ElementSelectors.byNameAndText)) // 匹配<foo>节点:按名称+value属性 .whenElementIsNamed("foo") .thenUse(ElementSelectors.byNameAndAttributes("value")) // 兜底规则:有文本的节点按名称+文本匹配,空节点按名称匹配 .elseUse(ElementSelectors.conditional() .when(ElementSelectors.hasText()) .thenUse(ElementSelectors.byNameAndText) .elseUse(ElementSelectors.byName)) .build() ) ) .withDifferenceEvaluator((comparison, outcome) -> { // 忽略generateDate节点的所有差异 String nodeName = null; Node controlNode = comparison.getControlDetails().getTarget(); Node testNode = comparison.getTestDetails().getTarget(); if (controlNode != null) { nodeName = controlNode.getNodeName(); } else if (testNode != null) { nodeName = testNode.getNodeName(); } if ("generateDate".equals(nodeName)) { return ComparisonResult.EQUAL; } return outcome; }) .build();
内容的提问来源于stack exchange,提问作者Andrei Manolache
相关产品推荐
相关产品推荐

