I need to compare two XML strings, where there is potentially changes to arbitrary attributes within a tag, as well as tags arbitrarily inserted into, or deleted from, a sequence of tags.
I have found that if I use ElementSelectors.byNameAndText I get appropriate diffs for attribute changes, unless there are sequence insertions or deletions.
I have found that if I use ElementSelectors.byNameAndAllAttributes I get appropriate diffs for tags inserted into, or deleted from, a sequence of tags, but not appropriate attributes change details.
How do I combine these two diff generating mechanisms?
Control XML
String myControlXML =
"<?xml version='1.0' encoding='UTF-8'?>" +
"<ns2:policy xmlns:ns2='com.xyz' version='2' id='ABCD'>" +
"<ns2:widgets>" +
"<ns2:widget name='foo' id='17'>" +
"<ns2:desc text='Does foo widget stuff' version='2'/>" +
"</ns2:widget>" +
"<ns2:widget name='bar' id='19'>" +
"<ns2:desc text='Does bar widget stuff' version='4'/>" +
"</ns2:widget>" +
"</ns2:widgets>" +
"</ns2:policy>";
Test XML
String myTestXML1 =
"<?xml version='1.0' encoding='UTF-8'?>" +
"<ns2:policy xmlns:ns2='com.xyz' version='13' id='ABCD'>" +
"<ns2:widgets>" +
"<ns2:widget name='foo' id='17'>" +
"<ns2:desc text='Does foo widget stuff' version='2'/>" +
"</ns2:widget>" +
"<ns2:widget name='bzz' id='15'>" +
"<ns2:desc text='Does bzz widget stuff' version='6'/>" +
"</ns2:widget>" +
"<ns2:widget name='bar' id='19'>" +
"<ns2:desc text='Does bar widget stuff' version='4'/>" +
"</ns2:widget>" +
"</ns2:widgets>" +
"</ns2:policy>";
Diff code
Diff myDiff7 = DiffBuilder.compare(myControlXML)
.withTest(myTestXML1)
.withNodeMatcher(new
DefaultNodeMatcher(ElementSelectors.byNameAndAllAttributes))
.build();
DIFF RESULTS 1
2 diffs
- type = child
- message = ns2:policy:Expected child '{com.xyz}policy' but was 'null' - comparing <ns2:policy...> at /policy[1] to <NULL>
- type = child
- message = ns2:policy:Expected child 'null' but was '{com.xyz}policy' - comparing <NULL> to <ns2:policy...> at /policy[1]
This seems to be saying 'some attribute is different'
With the ns2:policy lines matching,
DIFF RESULTS 2
2 diffs
- type = child nodelist sequence
- message = widget:Expected child nodelist sequence '1' but was '2' - comparing <ns2:widget...> at /policy[1]/widgets[1]/widget[2] to <ns2:widget...> at /policy[1]/widgets[1]/widget[3]
- type = child
- message = widget:Expected child 'null' but was '{com.xyz}widget' - comparing <NULL> to <ns2:widget...> at /policy[1]/widgets[1]/widget[2]
This seems to be correctly saying widget[2] was moved to widget[3] and a new widget[2] was added.
With the initial XML values, but using ElementSelectors.byNameAndText instead of ElementSelectors.byNameAndAllAttributes
DIFF RESULTS 3
6 diffs
- type = attribute value
- message = Expected attribute value '2' but was '13' - comparing <ns2:policy version="2"...> at /policy[1]/@version to <ns2:policy version="13"...> at /policy[1]/@version
- type = attribute value
- message = Expected attribute value '19' but was '15' - comparing <ns2:widget id="19"...> at /policy[1]/widgets[1]/widget[2]/@id to <ns2:widget id="15"...> at /policy[1]/widgets[1]/widget[2]/@id
- type = attribute value
- message = Expected attribute value 'bar' but was 'bzz' - comparing <ns2:widget name="bar"...> at /policy[1]/widgets[1]/widget[2]/@name to <ns2:widget name="bzz"...> at /policy[1]/widgets[1]/widget[2]/@name
- type = attribute value
- message = Expected attribute value 'Does bar widget stuff' but was 'Does bzz widget stuff' - comparing <ns2:desc text="Does bar widget stuff"...> at /policy[1]/widgets[1]/widget[2]/desc[1]/@text to <ns2:desc text="Does bzz widget stuff"...> at /policy[1]/widgets[1]/widget[2]/desc[1]/@text
- type = attribute value
- message = Expected attribute value '4' but was '6' - comparing <ns2:desc version="4"...> at /policy[1]/widgets[1]/widget[2]/desc[1]/@version to <ns2:desc version="6"...> at /policy[1]/widgets[1]/widget[2]/desc[1]/@version
- type = child
- message = Expected child 'null' but was '{com.xyz}widget' - comparing <NULL> to <ns2:widget...> at /policy[1]/widgets[1]/widget[3]
This seems to be correctly comparing the ns2:policy line, but is incorrectly comparing the old widget 2 to the new widget 2, and indicating a new widget[3] was added.
Thanks in advance for any help you can give me.
Edit:
I found a partial answer here XMLUnit-2.0 xpath doesn't ignoring the XML node order
Diff myDiff7a = DiffBuilder.compare(myControlXML)
.withTest(myTestXML1)
.withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.conditionalBuilder()
.whenElementIsNamed("widget").thenUse(
(e1, e2) -> StringUtils.equals(e1.getAttribute("name"), e2.getAttribute("name")))
.elseUse(ElementSelectors.byName)
.build()))
.build();
With the 'id' attribute of the 'bar' widget modified to 21
This produces the results:
3 diffs
- type = child nodelist sequence
- message = Expected child nodelist sequence '1' but was '2' - comparing <ns2:widget...> at /policy[1]/widgets[1]/widget[2] to <ns2:widget...> at /policy[1]/widgets[1]/widget[3]
- type = attribute value
- message = Expected attribute value '19' but was '21' - comparing <ns2:widget id="19"...> at /policy[1]/widgets[1]/widget[2]/@id to <ns2:widget id="21"...> at /policy[1]/widgets[1]/widget[3]/@id
- type = child
- message = Expected child 'null' but was '{com.xyz}widget' - comparing <NULL> to <ns2:widget...> at /policy[1]/widgets[1]/widget[2]
This seems to correctly be saying 'widget 2 was moved to widget 3, widget 2's id changed from 19 to widget 3's id value of 21, and a new widget 2 was added.'
The only thing still missing is how to do this for arbitrary tags and attributes.