From: <bo...@us...> - 2008-03-14 15:48:30
|
Revision: 250 http://xmlunit.svn.sourceforge.net/xmlunit/?rev=250&view=rev Author: bodewig Date: 2008-03-14 08:48:26 -0700 (Fri, 14 Mar 2008) Log Message: ----------- Add RecursiveElementNameAndTextQualifier contributed by Frank Callahan; Issue 1878549 Added Paths: ----------- trunk/xmlunit/src/java/org/custommonkey/xmlunit/examples/RecursiveElementNameAndTextQualifier.java trunk/xmlunit/tests/java/org/custommonkey/xmlunit/examples/test_RecursiveElementNameAndTextQualifier.java Added: trunk/xmlunit/src/java/org/custommonkey/xmlunit/examples/RecursiveElementNameAndTextQualifier.java =================================================================== --- trunk/xmlunit/src/java/org/custommonkey/xmlunit/examples/RecursiveElementNameAndTextQualifier.java (rev 0) +++ trunk/xmlunit/src/java/org/custommonkey/xmlunit/examples/RecursiveElementNameAndTextQualifier.java 2008-03-14 15:48:26 UTC (rev 250) @@ -0,0 +1,198 @@ +/* +****************************************************************** +Copyright (c) 2008, Jeff Martin, Tim Bacon +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of the xmlunit.sourceforge.net nor the names + of its contributors may be used to endorse or promote products + derived from this software without specific prior written + permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +****************************************************************** +*/ + +package org.custommonkey.xmlunit.examples; + +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import org.custommonkey.xmlunit.ElementNameQualifier; +import org.custommonkey.xmlunit.ElementQualifier; + +/** + * Compares all Element and Text nodes in two pieces of XML. Allows elements of + * complex, deeply nested types that are returned in different orders but have + * the same content to be recognized as comparable. + * + * @author Frank Callahan + */ +public class RecursiveElementNameAndTextQualifier implements ElementQualifier { + + private static final ElementNameQualifier NAME_QUALIFIER = + new ElementNameQualifier(); + + /** + * Uses element names and the text nested an arbitrary level of + * child elements deeper into the element to compare + * elements. Checks all nodes, not just first child element. + * + * <p> Does not ignore empty text nodes. + */ + public RecursiveElementNameAndTextQualifier() { + } + + /** + * Returns result of recursive comparison of all the nodes of a + * control and test element. + */ + public boolean qualifyForComparison(Element currentControl, + Element currentTest) { + return compareNodes(currentControl, currentTest); + } + + private boolean compareNodes(Node currentControl, Node currentTest) { + try { + + // if they are elements, compare names of the two nodes + if (!NAME_QUALIFIER.qualifyForComparison((Element) currentControl, + (Element) currentTest)) { + return false; + } + + // Compare the control and test elements' children + + NodeList controlNodes = null; + NodeList testNodes = null; + + // Check that both nodes have children and, if so, get lists of them + + if (currentControl.hasChildNodes() && currentTest.hasChildNodes()) { + controlNodes = currentControl.getChildNodes(); + testNodes = currentTest.getChildNodes(); + } else if (currentControl.hasChildNodes() + || currentTest.hasChildNodes()) { + return false; + + // if both nodes are empty, they are comparable + } else { + return true; + } + + // check that both node lists have the same length + + if (countNodesWithoutConsecutiveTextNodes(controlNodes) + != countNodesWithoutConsecutiveTextNodes(testNodes)) { + return false; + } + + // Do checks of test and control nodes' children + + final int cNodes = controlNodes.getLength(); + final int tNodes = testNodes.getLength(); + + int i, j; + for (i = j = 0; i < cNodes && j < tNodes; i++, j++) { + Node testNode = testNodes.item(i); + Node controlNode = controlNodes.item(j); + + // check if both node are same type + if (controlNode.getNodeType() != testNode.getNodeType()) { + return false; + } + // compare text nodes + if (controlNode.getNodeType() == Node.TEXT_NODE) { + // compare concatenated, trimmed text nodes + if (!catText(controlNode).equals(catText(testNode))) { + return false; + } + + // swallow adjacent Text nodes + for (; i < cNodes - 1 + && controlNodes.item(i + 1).getNodeType() == Node.TEXT_NODE; + i++); + for (; j < tNodes - 1 + && testNodes.item(j + 1).getNodeType() == Node.TEXT_NODE; + j++); + + // recursive check of current child control and test nodes' + // children + + } else if (!compareNodes((Element) controlNode, + (Element) testNode)) { + return false; + } + } + if (i != cNodes || j != tNodes) { + return false; + } + + // All descendants of current control and test nodes are comparable + return true; + } catch (Exception e) { + return false; + } + } + /** + * Concatenates contiguous Text nodes and removes all leading and + * trailing whitespace. + * @param textNode + * @return + */ + private static String catText(Node textNode) { + StringBuffer text = new StringBuffer(); + Node next = textNode; + + do { + if (next.getNodeValue() != null) { + text.append(next.getNodeValue().trim()); + next = next.getNextSibling(); + } + } while (next != null && next.getNodeType() == Node.TEXT_NODE); + + return text.toString(); + } + + /** + * Calculates the number of Nodes that are either not Text nodes + * or are Text nodes whose previous sibling isn't a Text node as + * well. I.e. consecutive Text nodes are counted as a single + * node. + */ + private static int countNodesWithoutConsecutiveTextNodes(NodeList l) { + int count = 0; + boolean lastNodeWasText = false; + final int length = l.getLength(); + for (int i = 0; i < length; i++) { + Node n = l.item(i); + if (!lastNodeWasText || n.getNodeType() != Node.TEXT_NODE) { + count++; + } + lastNodeWasText = n.getNodeType() == Node.TEXT_NODE; + } + return count; + } +} \ No newline at end of file Property changes on: trunk/xmlunit/src/java/org/custommonkey/xmlunit/examples/RecursiveElementNameAndTextQualifier.java ___________________________________________________________________ Name: svn:eol-style + native Added: trunk/xmlunit/tests/java/org/custommonkey/xmlunit/examples/test_RecursiveElementNameAndTextQualifier.java =================================================================== --- trunk/xmlunit/tests/java/org/custommonkey/xmlunit/examples/test_RecursiveElementNameAndTextQualifier.java (rev 0) +++ trunk/xmlunit/tests/java/org/custommonkey/xmlunit/examples/test_RecursiveElementNameAndTextQualifier.java 2008-03-14 15:48:26 UTC (rev 250) @@ -0,0 +1,173 @@ +/* +****************************************************************** +Copyright (c) 2008, Jeff Martin, Tim Bacon +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of the xmlunit.sourceforge.net nor the names + of its contributors may be used to endorse or promote products + derived from this software without specific prior written + permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +****************************************************************** +*/ + +package org.custommonkey.xmlunit.examples; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import junit.framework.TestCase; +import junit.framework.TestSuite; + +import org.custommonkey.xmlunit.Diff; +import org.custommonkey.xmlunit.ElementNameAndTextQualifier; +import org.custommonkey.xmlunit.ElementQualifier; +import org.custommonkey.xmlunit.XMLUnit; + +/** + * JUnit testcase for RecursiveElementNameAndTextQualifier with most + * tests copied from MultiLevelElementNameAndTextQualifier' test. + * @see test_Diff#testRepeatedElementNamesWithTextQualification() + */ +public class test_RecursiveElementNameAndTextQualifier extends TestCase { + private static final String TAG_NAME = "tagYoureIt"; + private static final String TAG_NAME2 = "tagYoureIt2"; + private static final String TEXT_A = "textA"; + private static final String TEXT_B = "textB"; + private Document document; + + // copy of ElementNameAndTextQualifier test + public void testSingleTextValue() throws Exception { + ElementQualifier qualifier = + new RecursiveElementNameAndTextQualifier(); + + Element control = document.createElement(TAG_NAME); + control.appendChild(document.createTextNode(TEXT_A)); + + Element test = document.createElement(TAG_NAME); + + assertFalse("control text not comparable to empty text", + qualifier.qualifyForComparison(control, test)); + + test.appendChild(document.createTextNode(TEXT_A)); + assertTrue("control textA comparable to test textA", + qualifier.qualifyForComparison(control, test)); + + test = document.createElement(TAG_NAME); + + test.appendChild(document.createTextNode(TEXT_B)); + assertFalse("control textA not comparable to test textB", + qualifier.qualifyForComparison(control, test)); + } + + // copy of ElementNameAndTextQualifier test + public void testMultipleTextValues() throws Exception { + ElementQualifier qualifier = + new RecursiveElementNameAndTextQualifier(); + + Element control = document.createElement(TAG_NAME); + control.appendChild(document.createTextNode(TEXT_A)); + control.appendChild(document.createTextNode(TEXT_B)); + + Element test = document.createElement(TAG_NAME); + test.appendChild(document.createTextNode(TEXT_A + TEXT_B)); + assertTrue("denormalised control text comparable to normalised test text", + qualifier.qualifyForComparison(control, test)); + } + + // three levels + public void testThreeLevels() throws Exception { + ElementQualifier qualifier = + new RecursiveElementNameAndTextQualifier(); + + Element control = document.createElement(TAG_NAME); + Element child = document.createElement(TAG_NAME2); + control.appendChild(child); + Element child2 = document.createElement(TAG_NAME); + child.appendChild(child2); + child2.appendChild(document.createTextNode(TEXT_B)); + + Element test = document.createElement(TAG_NAME); + child = document.createElement(TAG_NAME2); + test.appendChild(child); + child2 = document.createElement(TAG_NAME); + child.appendChild(child2); + child2.appendChild(document.createTextNode(TEXT_B)); + + assertTrue(qualifier.qualifyForComparison(control, test)); + } + + /** + * @see https://sourceforge.net/forum/forum.php?thread_id=1440169&forum_id=73274 + */ + public void testThread1440169() throws Exception { + String s1 = "<a><b><c>foo</c></b><b><c>bar</c></b></a>"; + String s2 = "<a><b><c>bar</c></b><b><c>foo</c></b></a>"; + Diff d = new Diff(s1, s2); + assertFalse(d.similar()); + + // reset + d = new Diff(s1, s2); + d.overrideElementQualifier(new ElementNameAndTextQualifier()); + assertFalse(d.similar()); + + // reset once again + d = new Diff(s1, s2); + d.overrideElementQualifier(new RecursiveElementNameAndTextQualifier()); + assertTrue(d.similar()); + + } + + public void testUserGuideExample() throws Exception { + String control = + "<table>\n" + + " <tr>\n" + + " <td>foo</td>\n" + + " </tr>\n" + + " <tr>\n" + + " <td>bar</td>\n" + + " </tr>\n" + + "</table>\n"; + String test = + "<table>\n" + + " <tr>\n" + + " <td>bar</td>\n" + + " </tr>\n" + + " <tr>\n" + + " <td>foo</td>\n" + + " </tr>\n" + + "</table>\n"; + + Diff d = new Diff(control, test); + d.overrideElementQualifier(new RecursiveElementNameAndTextQualifier()); + assertTrue(d.toString(), d.similar()); + } + + public void setUp() throws Exception { + document = XMLUnit.newControlParser().newDocument(); + } + +} Property changes on: trunk/xmlunit/tests/java/org/custommonkey/xmlunit/examples/test_RecursiveElementNameAndTextQualifier.java ___________________________________________________________________ Name: svn:eol-style + native This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |