1   /*
2    * Copyright 2001-2004 The Apache Software Foundation.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */ 
16   
17  package org.apache.commons.betwixt.xmlunit;
18  
19  import java.io.File;
20  import java.io.IOException;
21  import java.io.StringReader;
22  import java.io.StringWriter;
23  import java.util.ArrayList;
24  import java.util.Collections;
25  import java.util.Comparator;
26  import java.util.Iterator;
27  import java.util.List;
28  
29  import javax.xml.parsers.DocumentBuilder;
30  import javax.xml.parsers.DocumentBuilderFactory;
31  import javax.xml.parsers.ParserConfigurationException;
32  
33  import junit.framework.AssertionFailedError;
34  import junit.framework.TestCase;
35  
36  import org.w3c.dom.Attr;
37  import org.w3c.dom.DOMException;
38  import org.w3c.dom.Document;
39  import org.w3c.dom.NamedNodeMap;
40  import org.w3c.dom.Node;
41  import org.w3c.dom.NodeList;
42  import org.xml.sax.InputSource;
43  import org.xml.sax.SAXException;
44  
45  /*** 
46    * Provides xml test utilities. 
47    * Hopefully, these might be moved into [xmlunit] sometime.
48    *
49    * @author Robert Burrell Donkin
50    */
51  public class XmlTestCase extends TestCase {
52  
53      protected static boolean debug = false;
54  
55      DocumentBuilderFactory domFactory;
56  
57           
58      public XmlTestCase(String testName) {
59          super(testName);
60      }
61  
62      
63      public void xmlAssertIsomorphicContent(
64                                  org.w3c.dom.Document documentOne, 
65                                  org.w3c.dom.Document documentTwo)
66                                      throws 
67                                          AssertionFailedError {
68          log("Testing documents:" + documentOne.getDocumentElement().getNodeName() 
69              + " and " + documentTwo.getDocumentElement().getNodeName());
70          xmlAssertIsomorphicContent(documentOne, documentTwo, false);
71      }
72  
73      public void xmlAssertIsomorphicContent(
74                                  org.w3c.dom.Document documentOne, 
75                                  org.w3c.dom.Document documentTwo,
76                                  boolean orderIndependent)
77                                      throws 
78                                          AssertionFailedError {
79          xmlAssertIsomorphicContent(null, documentOne, documentTwo, orderIndependent);
80      }
81      
82      public void xmlAssertIsomorphicContent(
83                                  String message,
84                                  org.w3c.dom.Document documentOne, 
85                                  org.w3c.dom.Document documentTwo)
86                                      throws 
87                                          AssertionFailedError {
88          
89          xmlAssertIsomorphicContent(message, documentOne, documentTwo, false);
90      }
91      
92      public void xmlAssertIsomorphicContent(
93                                  String message,
94                                  org.w3c.dom.Document documentOne, 
95                                  org.w3c.dom.Document documentTwo,
96                                  boolean orderIndependent)
97                                      throws 
98                                          AssertionFailedError
99      {
100         // two documents have isomorphic content iff their root elements 
101         // are isomophic
102         xmlAssertIsomorphic(
103                             message, 
104                             documentOne.getDocumentElement(), 
105                             documentTwo.getDocumentElement(),
106                             orderIndependent);
107     }
108  
109     
110     public void xmlAssertIsomorphic(
111                                 org.w3c.dom.Node rootOne, 
112                                 org.w3c.dom.Node rootTwo) 
113                                     throws 
114                                         AssertionFailedError {
115         xmlAssertIsomorphic(rootOne, rootTwo, false);
116     }
117     
118     public void xmlAssertIsomorphic(
119                                 org.w3c.dom.Node rootOne, 
120                                 org.w3c.dom.Node rootTwo,
121                                 boolean orderIndependent)
122                                     throws 
123                                         AssertionFailedError
124     {
125         xmlAssertIsomorphic(null, rootOne, rootTwo, orderIndependent);
126     }
127     
128     public void xmlAssertIsomorphic(
129                                 String message,
130                                 org.w3c.dom.Node rootOne, 
131                                 org.w3c.dom.Node rootTwo) {
132                                 
133         xmlAssertIsomorphic(message, rootOne, rootTwo, false);
134     
135     }
136     
137     public void xmlAssertIsomorphic(
138                                 String message,
139                                 org.w3c.dom.Node rootOne, 
140                                 org.w3c.dom.Node rootTwo,
141                                 boolean orderIndependent)
142                                     throws 
143                                         AssertionFailedError
144     {
145         // first normalize the xml
146         rootOne.normalize();
147         rootTwo.normalize();
148         // going to use recursion so avoid normalizing each time
149         testIsomorphic(message, rootOne, rootTwo, orderIndependent);
150     }
151  
152     
153     private void testIsomorphic(
154                                 String message,
155                                 org.w3c.dom.Node nodeOne, 
156                                 org.w3c.dom.Node nodeTwo)
157                                     throws 
158                                         AssertionFailedError {
159                                         
160         testIsomorphic(message, nodeOne, nodeTwo, false);
161     }
162                                     
163     
164     private void testIsomorphic(
165                                 String message,
166                                 org.w3c.dom.Node nodeOne, 
167                                 org.w3c.dom.Node nodeTwo,
168                                 boolean orderIndependent)
169                                     throws 
170                                         AssertionFailedError
171     {
172         try {
173             if (debug) {
174                 log(
175                     "node 1 name=" + nodeOne.getNodeName() 
176                     + " qname=" + nodeOne.getLocalName());
177                 log(
178                     "node 2 name=" + nodeTwo.getNodeName() 
179                     + " qname=" + nodeTwo.getLocalName());
180             }
181             
182             // compare node properties
183             log("Comparing node properties");
184             assertEquals(
185                         (null == message ? "(Unequal node types)" : message + "(Unequal node types)"), 
186                         nodeOne.getNodeType(), 
187                         nodeTwo.getNodeType());
188             assertEquals(
189                         (null == message ? "(Unequal node names)" : message + "(Unequal node names)"), 
190                         nodeOne.getNodeName(), 
191                         nodeTwo.getNodeName());
192             assertEquals(
193                         (null == message ? "(Unequal node values)" : message + "(Unequal node values)"), 
194                         trim(nodeOne.getNodeValue()), 
195                         trim(nodeTwo.getNodeValue()));
196             assertEquals(
197                         (null == message ? "(Unequal local names)" : message + "(Unequal local names)"), 
198                         nodeOne.getLocalName(), 
199                         nodeTwo.getLocalName());
200             assertEquals(
201                         (null == message ? "(Unequal namespace)" : message + "(Unequal namespace)"), 
202                         nodeOne.getNamespaceURI(), 
203                         nodeTwo.getNamespaceURI());
204             
205                                                   
206             // compare attributes
207             log("Comparing attributes");
208             // make sure both have them first
209             assertEquals(
210                         (null == message ? "(Unequal attributes)" : message + "(Unequal attributes)"), 
211                         nodeOne.hasAttributes(), 
212                         nodeTwo.hasAttributes());            
213             if (nodeOne.hasAttributes()) {
214                 // do the actual comparison
215                 // first we check the number of attributes are equal 
216                 // we then check that for every attribute of node one, 
217                 // a corresponding attribute exists in node two
218                 // (this should be sufficient to prove equality)
219                 NamedNodeMap attributesOne = nodeOne.getAttributes();
220                 NamedNodeMap attributesTwo = nodeTwo.getAttributes();
221                 
222                 assertEquals(
223                         (null == message ? "(Unequal attributes)" : message + "(Unequal attributes)"), 
224                         attributesOne.getLength(), 
225                         attributesTwo.getLength());
226                 
227                 for (int i=0, size=attributesOne.getLength(); i<size; i++) {
228                     Attr attributeOne = (Attr) attributesOne.item(i);
229                     Attr attributeTwo = (Attr) attributesTwo.getNamedItemNS(
230                                                     attributeOne.getNamespaceURI(),
231                                                     attributeOne.getLocalName());
232                     if (attributeTwo == null) {
233                         attributeTwo = (Attr) attributesTwo.getNamedItem(attributeOne.getName());
234                     }
235                     
236                     // check attribute two exists
237                     if (attributeTwo == null) {
238                         String diagnosis = "[Missing attribute (" + attributeOne.getName() +  ")]";
239                         fail((null == message ?  diagnosis : message + diagnosis));
240                     }
241                     
242                     // now check attribute values
243                     assertEquals(
244                         (null == message ? "(Unequal attribute values)" : message + "(Unequal attribute values)"), 
245                         attributeOne.getValue(), 
246                         attributeTwo.getValue());                    
247                 }
248             }
249             
250             
251             // compare children
252             log("Comparing children");
253             // this time order is important
254             // so we can just go down the list and compare node-wise using recursion
255             List listOne = sanitize(nodeOne.getChildNodes());
256             List listTwo = sanitize(nodeTwo.getChildNodes());
257 
258             if (orderIndependent) {
259                 log("[Order Independent]");
260                 Comparator nodeByName = new NodeByNameComparator();
261                 Collections.sort(listOne, nodeByName);
262                 Collections.sort(listTwo, nodeByName);
263             }
264             
265             Iterator it = listOne.iterator();
266             Iterator iter2 = listTwo.iterator();
267             while (it.hasNext() & iter2.hasNext()) {
268                 Node nextOne = ((Node)it.next());
269                 Node nextTwo = ((Node)iter2.next());
270                 log(nextOne.getNodeName() + ":" + nextOne.getNodeValue());
271                 log(nextTwo.getNodeName() + ":" + nextTwo.getNodeValue());
272             }
273 
274             assertEquals(
275                         (null == message ? "(Unequal child nodes@" + nodeOne.getNodeName() +")": 
276                                 message + "(Unequal child nodes @" + nodeOne.getNodeName() +")"), 
277                         listOne.size(), 
278                         listTwo.size());           
279                         
280             it = listOne.iterator();
281             iter2 = listTwo.iterator();
282             while (it.hasNext() & iter2.hasNext()) {	
283                 Node nextOne = ((Node)it.next());
284                 Node nextTwo = ((Node)iter2.next());
285                 log(nextOne.getNodeName() + " vs " + nextTwo.getNodeName());
286                 testIsomorphic(message, nextOne, nextTwo, orderIndependent);
287             
288             }
289         
290         } catch (DOMException ex) {
291             fail((null == message ? "" : message + " ") + "DOM exception" + ex.toString());
292         }
293     }
294     
295     
296     protected DocumentBuilder createDocumentBuilder() {
297         try {
298 
299             return getDomFactory().newDocumentBuilder();
300         
301         } catch (ParserConfigurationException e) {
302             fail("Cannot create DOM builder: " + e.toString());
303         
304         }
305         // just to keep the compiler happy
306         return null;
307     }
308     
309     protected DocumentBuilderFactory getDomFactory() {
310         // lazy creation
311         if (domFactory == null) {
312             domFactory = DocumentBuilderFactory.newInstance();
313         }
314         
315         return domFactory;
316     }
317     
318     protected Document parseString(StringWriter writer) {
319         try { 
320         
321             return createDocumentBuilder().parse(new InputSource(new StringReader(writer.getBuffer().toString())));
322         
323         } catch (SAXException e) {
324             fail("Cannot create parse string: " + e.toString());
325         
326         } catch (IOException e) {
327             fail("Cannot create parse string: " + e.toString());
328         
329         } 
330         // just to keep the compiler happy
331         return null;
332     }
333     
334     protected Document parseString(String string) {
335         try { 
336         
337             return createDocumentBuilder().parse(new InputSource(new StringReader(string)));
338         
339         } catch (SAXException e) {
340             fail("Cannot create parse string: " + e.toString());
341         
342         } catch (IOException e) {
343             fail("Cannot create parse string: " + e.toString());
344         
345         } 
346         // just to keep the compiler happy
347         return null;
348     }
349 
350     
351     protected Document parseFile(String path) {
352         try { 
353         
354             return createDocumentBuilder().parse(new File(path));
355         
356         } catch (SAXException e) {
357             fail("Cannot create parse file: " + e.toString());
358         
359         } catch (IOException e) {
360             fail("Cannot create parse file: " + e.toString());
361         
362         } 
363         // just to keep the compiler happy
364         return null;
365     }
366     
367     private void log(String message)
368     {
369         if (debug) {
370             System.out.println("[XmlTestCase]" + message);
371         }
372     }
373     
374     private String trim(String trimThis)
375     {
376         if (trimThis == null) {
377             return trimThis;
378         }
379         
380         return trimThis.trim();
381     }
382     
383     private List sanitize(NodeList nodes) {
384         ArrayList list = new ArrayList();
385         
386         for (int i=0, size=nodes.getLength(); i<size; i++) {
387             if ( nodes.item(i).getNodeType() == Node.TEXT_NODE ) {
388                 if ( !( nodes.item(i).getNodeValue() == null ||  
389                         nodes.item(i).getNodeValue().trim().length() == 0 )) {
390                     list.add(nodes.item(i));
391                 } else {
392                     log("Ignoring text node:" + nodes.item(i).getNodeValue());
393                 }
394             } else {
395                 list.add(nodes.item(i));
396             }
397         }
398         return list;
399     }
400     
401     private class NodeByNameComparator implements Comparator {
402         public int compare(Object objOne, Object objTwo) {
403             String nameOne = ((Node) objOne).getNodeName();
404             String nameTwo = ((Node) objTwo).getNodeName();
405             
406             if (nameOne == null) {
407                 if (nameTwo == null) {
408                     return 0;
409                 }
410                 return -1;
411             }
412             
413             if (nameTwo == null) {
414                 return 1;
415             }
416             
417             return nameOne.compareTo(nameTwo);
418         }
419     }
420 }
421