Menu

Ignore node (and the node's attributes and child differences) when the control doc's node has a 'lastupdatedby' attribute

Help
2017-12-27
2018-01-03
  • Vairavan Vairavan

    Hello,
    I am using XMLUnit 2.x for comparing two XMLs. I have a requirement to compare two XMLs (Control.xml and Test.xml) and if any of the nodes in the Control.xml has an attribute with the name 'lastupdatedby', then I should ignore any type of differences generated on that node (which contains 'lastupdatedby' attribute). Please let me know how can I implement this.

    I tried NodeFilter but it is of no luck since the Test.xml will NOT have any nodes with 'lastupdatedby' attribute. The 'lastupdatedby' attribute will ONLY be present in the Control.xml.

    Control.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <books>
        <book range="highcls" id="1" lastupdatedby="chetan">
            <name>Angels &amp; Demons</name>
            <isbn>9971-5-0210-0</isbn>
            <author>Dan Browns</author>
            <category></category>
        </book>
        <book>
            <name>You can win</name>
            <isbn>9971-5-0282-0</isbn>
            <author lastupdatedby ="Vairav">Shiv Khera</author>
        </book>
    </books>
    

    Test.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <books>
        <book id="2" range="high">
            <name>Angels &amp; Demons</name>
            <isbn>9971-5-0210-0</isbn>
            <Type><history></history></Type>
          <category>histoty</category>
        </book>
        <book>
            <name>You can win</name>
            <Price>Testy</Price>
            <author>Shiv Kheras</author>
        </book>
        <Library>Public</Library>
    </books>
    

    These are two sample XMLs. Based on my requirement, I only need the differences on the second <book> item (except the <author> since it also has 'lastupdatedby' attribute) and <library> item in the <books> list.</books></library></author></book>

    Please help me identify a solution for my requirement.
    Once I am able to ignore any differences on the nodes with ''lastupdatedby" attribute, I have written custom code to loop through the differences and merge the Control.xml and Test.xml into Merge.xml.

    My end goal is to merge Control.xml and Test.xml into Merge.xml and the requirement is to replace the values (even appending childs from Test.xml) from Test.xml into Control.xml and create a new file called 'Merge.xml'. The only exception for this merge is that, I should NOT overwrite any nodes in the Control.xml which has 'lastupdatedby' attribute. If there is any other API in XMLUnit to merge two XMLs, please point me to that as well.

    Thanks in Advance for your help!

    Thanks,
    Vairavan.

     
  • Stefan Bodewig

    Stefan Bodewig - 2017-12-28

    This is a tough requirement.

    One approach may be to implement a DifferenceEvaluator and for every real difference walk up the tree starting from the Comparison's control node checking whether any node contains the attribute and return "EQUAL" in this case.

     
  • Vairavan Vairavan

    Thanks Stefan,
    Can you please help me with this particular DifferenceEvaluator implementation? I am new to this and any help on this would be of great help to me.

    Thanks,
    Vairavan

     
  • Stefan Bodewig

    Stefan Bodewig - 2018-01-03

    Actually this is probably more complex than I though.

    import org.w3c.dom.*;
    import org.xmlunit.builder.*;
    import org.xmlunit.diff.*;
    
    public class Ignore {
        private static class IgnoreDifferenceEvaluator implements DifferenceEvaluator {
            public ComparisonResult evaluate(Comparison comparison, ComparisonResult outcome) {
                if (isIgnorable(comparison)) {
                    return ComparisonResult.EQUAL;
                }
                return outcome;
            }
    
            private boolean isIgnorable(Comparison comparison) {
                return comparison.getControlDetails() != null
                    && isIgnorable(comparison.getControlDetails().getTarget());
            }
    
            private boolean isIgnorable(Node n) {
                if (n == null) {
                    return false;
                }
                return (n instanceof Attr && isIgnorable((Attr) n))
                    || (n instanceof Element && isIgnorable((Element) n))
                    || isIgnorable(n.getParentNode());
            }
    
            private boolean isIgnorable(Attr a) {
                return isIgnorable((Node) a.getOwnerElement());
            }
    
            private boolean isIgnorable(Element e) {
                return e.getAttribute("lastupdatedby") != null;
            }
        }
    
        private static final String CONTROL = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
    
            + "<books>"
            + "    <book range=\"highcls\" id=\"1\" lastupdatedby=\"chetan\">"
            + "        <name>Angels &amp; Demons</name>"
            + "        <isbn>9971-5-0210-0</isbn>"
            + "        <author>Dan Browns</author>"
            + "        <category></category>"
            + "    </book>"
            + "    <book>"
            + "        <name>You can win</name>"
            + "        <isbn>9971-5-0282-0</isbn>"
            + "        <author lastupdatedby =\"Vairav\">Shiv Khera</author>"
            + "    </book>"
            + "</books>";
        private static final String TEST = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
            + "<books>"
            + "    <book id=\"2\" range=\"high\">"
            + "        <name>Angels &amp; Demons</name>"
            + "        <isbn>9971-5-0210-0</isbn>"
            + "        <Type><history></history></Type>"
            + "      <category>histoty</category>"
            + "    </book>"
            + "    <book>"
            + "        <name>You can win</name>"
            + "        <Price>Testy</Price>"
            + "        <author>Shiv Kheras</author>"
            + "    </book>"
            + "    <Library>Public</Library>"
            + "</books>";
    
        public static void main(String[] args) throws Exception {
            Diff diff = DiffBuilder.compare(CONTROL)
                .withTest(TEST)
                .ignoreWhitespace()
                .withDifferenceEvaluator(new IgnoreDifferenceEvaluator())
                .build();
            for (Difference d : diff.getDifferences()) {
                System.err.println(d);
            }
        }
    }
    

    leads to

    Expected child 'null' but was 'history' - comparing <NULL> to <history...> at /books[1]/book[1]/Type[1]/history[1] (DIFFERENT)
    Expected child 'null' but was '#text' - comparing <NULL> to <category ...>histoty</category> at /books[1]/book[1]/category[1]/text()[1] (DIFFERENT)
    Expected child 'null' but was 'Library' - comparing <NULL> to <Library...> at /books[1]/Library[1] (DIFFERENT)
    

    which contains more differences than you'd like to see (the added Type element and the text difference in category). They happen because there is no "control node" one could traverse up.

    You could cache the XPath of nodes that have been considered ignorable and ignore all comparisons with an XPath that starts with such an ignorable path. I think you can go from here.

     

    Last edit: Stefan Bodewig 2018-01-03

Log in to post a comment.

MongoDB Logo MongoDB