KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > tonbeller > tbutils > httpunit > XmlDiff


1 package com.tonbeller.tbutils.httpunit;
2
3 import java.io.File JavaDoc;
4 import java.io.IOException JavaDoc;
5 import java.net.MalformedURLException JavaDoc;
6 import java.net.URL JavaDoc;
7 import java.text.ParseException JavaDoc;
8 import java.text.SimpleDateFormat JavaDoc;
9 import java.util.ArrayList JavaDoc;
10 import java.util.Iterator JavaDoc;
11 import java.util.List JavaDoc;
12 import java.util.StringTokenizer JavaDoc;
13 import java.util.regex.PatternSyntaxException JavaDoc;
14
15 import javax.xml.parsers.DocumentBuilder JavaDoc;
16 import javax.xml.parsers.DocumentBuilderFactory JavaDoc;
17 import javax.xml.parsers.FactoryConfigurationError JavaDoc;
18 import javax.xml.parsers.ParserConfigurationException JavaDoc;
19 import javax.xml.transform.Result JavaDoc;
20 import javax.xml.transform.Source JavaDoc;
21 import javax.xml.transform.Transformer JavaDoc;
22 import javax.xml.transform.TransformerException JavaDoc;
23 import javax.xml.transform.TransformerFactory JavaDoc;
24 import javax.xml.transform.dom.DOMResult JavaDoc;
25 import javax.xml.transform.dom.DOMSource JavaDoc;
26 import javax.xml.transform.stream.StreamResult JavaDoc;
27 import javax.xml.transform.stream.StreamSource JavaDoc;
28
29 import org.w3c.dom.Attr JavaDoc;
30 import org.w3c.dom.Document JavaDoc;
31 import org.w3c.dom.Element JavaDoc;
32 import org.w3c.dom.NamedNodeMap JavaDoc;
33 import org.w3c.dom.Node JavaDoc;
34 import org.w3c.dom.NodeList JavaDoc;
35 import org.w3c.dom.Text JavaDoc;
36 import org.xml.sax.SAXException JavaDoc;
37
38 /**
39  * Compares XML files by structure. Significant are Element nodes (ordered), Attribute nodes
40  * (unordered) and Text nodes (optional ignoring whitespace). Everything else is ignored
41  * (comments, formatting etc).
42  *
43  * @author av
44  */

45 public class XmlDiff {
46
47   private IDiffListener diffListener = null;
48
49   public interface EqualsComparator {
50     boolean equals(Object JavaDoc o1, Object JavaDoc o2);
51   }
52
53   public static class DefaultEqualsComparator implements EqualsComparator {
54     public boolean equals(Object JavaDoc o1, Object JavaDoc o2) {
55       return o1.equals(o2);
56     }
57   }
58
59   public static class IntegerEqualsComparator implements EqualsComparator {
60     public boolean equals(Object JavaDoc o1, Object JavaDoc o2) {
61       try {
62         String JavaDoc s1 = ((String JavaDoc) o1).trim();
63         String JavaDoc s2 = ((String JavaDoc) o2).trim();
64         int i1 = Integer.parseInt(s1);
65         int i2 = Integer.parseInt(s2);
66         return i1 == i2;
67       } catch (NumberFormatException JavaDoc e) {
68         return o1.equals(o2);
69       }
70     }
71   }
72
73   public static class IntegerLeadingZerosEqualsComparator implements EqualsComparator {
74
75     private String JavaDoc delimiter;
76
77     public IntegerLeadingZerosEqualsComparator(String JavaDoc delimiter) {
78       this.delimiter = delimiter;
79     }
80
81     public IntegerLeadingZerosEqualsComparator() {
82       this("/");
83     }
84
85     // xxxx/0000123/0004 is equal to xxxx/123/4
86
// 000123 is equal to 123
87
public boolean equals(Object JavaDoc o1, Object JavaDoc o2) {
88       try {
89         String JavaDoc s1 = ((String JavaDoc) o1).trim();
90         String JavaDoc s2 = ((String JavaDoc) o2).trim();
91         int i1 = Integer.parseInt(s1);
92         int i2 = Integer.parseInt(s2);
93         return i1 == i2;
94       } catch (NumberFormatException JavaDoc e) {
95         // Format with slashes, check each substring
96
StringTokenizer JavaDoc st1 = new StringTokenizer JavaDoc(((String JavaDoc) o1).trim(), delimiter);
97         StringTokenizer JavaDoc st2 = new StringTokenizer JavaDoc(((String JavaDoc) o2).trim(), delimiter);
98         if (st1.countTokens() != st2.countTokens())
99           return false;
100
101         XmlDiff.EqualsComparator ec = new XmlDiff.IntegerEqualsComparator();
102         while (st1.hasMoreTokens()) {
103           if (!ec.equals(st1.nextToken(), st2.nextToken()))
104             return false;
105         }
106         return true;
107       }
108     }
109   }
110
111   public static class RegularExpressionEqualsComparator implements EqualsComparator {
112     // Example: cust = ' 12' == cust = 12, perl pattern = s/[ ']+/ /g
113
String JavaDoc regex;
114     String JavaDoc replacement;
115
116     public RegularExpressionEqualsComparator(String JavaDoc regex, String JavaDoc replacement) {
117       this.regex = regex;
118       this.replacement = replacement;
119     }
120
121     public boolean equals(Object JavaDoc o1, Object JavaDoc o2) {
122       try {
123         String JavaDoc s1 = ((String JavaDoc) o1).replaceAll(regex, replacement);
124         String JavaDoc s2 = ((String JavaDoc) o2).replaceAll(regex, replacement);
125         return s1.equals(s2);
126       } catch (PatternSyntaxException JavaDoc e) {
127         return o1.equals(o2);
128       }
129     }
130   }
131
132   /**
133    * Beinhaltet den IntegerLeadingZerosEquals-Comparator und
134    * ignoriert zusätzlich Date/Time-Konstrukte
135    */

136   public static class DateTimeIgnoreIntegerLeadingZerosEqualsComparator implements EqualsComparator {
137     private String JavaDoc pattern;
138     private String JavaDoc delimiter;
139
140     public DateTimeIgnoreIntegerLeadingZerosEqualsComparator() {
141       this("", "/");
142     }
143
144     public DateTimeIgnoreIntegerLeadingZerosEqualsComparator(String JavaDoc pattern, String JavaDoc delimiter) {
145       if (pattern != null)
146         this.pattern = pattern;
147       else
148         this.pattern = "";
149
150       if (delimiter != null)
151         this.delimiter = delimiter;
152       else
153         this.delimiter = "";
154     }
155
156     public boolean equals(Object JavaDoc o1, Object JavaDoc o2) {
157
158       // "15.12.2004 12:34 Demo" equals to "15.12.2005 12:35 Demo"
159
String JavaDoc str1 = ((String JavaDoc) o1).trim();
160       String JavaDoc str2 = ((String JavaDoc) o2).trim();
161       int len = pattern.length();
162
163       if ((str1.length() >= len) && (str2.length() >= len)) {
164         // may be a date/time fragment
165
try {
166           SimpleDateFormat JavaDoc formatter = new SimpleDateFormat JavaDoc(pattern);
167           formatter.parse(str1.substring(0, len));
168           formatter.parse(str2.substring(0, len));
169
170           // date/time fragment found: ignore this part and check the rest
171
str1 = str1.substring(len);
172           str2 = str2.substring(len);
173           return str1.equals(str2);
174         } catch (ParseException JavaDoc pe) {
175           // not a valid date/time: try next comparator
176
}
177       }
178
179       // xxxx/0000123/0004 equals to xxxx/123/4
180
// 000123 equals to 123
181
try {
182         String JavaDoc s1 = ((String JavaDoc) o1).trim();
183         String JavaDoc s2 = ((String JavaDoc) o2).trim();
184         int i1 = Integer.parseInt(s1);
185         int i2 = Integer.parseInt(s2);
186         return i1 == i2;
187       } catch (NumberFormatException JavaDoc e) {
188         // Format with slashes, check each substring
189
StringTokenizer JavaDoc st1 = new StringTokenizer JavaDoc(((String JavaDoc) o1).trim(), delimiter);
190         StringTokenizer JavaDoc st2 = new StringTokenizer JavaDoc(((String JavaDoc) o2).trim(), delimiter);
191         if (st1.countTokens() != st2.countTokens())
192           return false;
193
194         XmlDiff.EqualsComparator ec = new XmlDiff.IntegerEqualsComparator();
195         while (st1.hasMoreTokens()) {
196           if (!ec.equals(st1.nextToken(), st2.nextToken()))
197             return false;
198         }
199         return true;
200       }
201     }
202   }
203
204   private boolean ignoreWhitespace;
205   private String JavaDoc xslName;
206
207   // compares value of Text nodes
208
private EqualsComparator textComparator = new DefaultEqualsComparator();
209   // compares value of Attr nodes
210
private EqualsComparator attrComparator = new DefaultEqualsComparator();
211
212   public XmlDiff(boolean ignoreWhitespace) {
213     this.ignoreWhitespace = ignoreWhitespace;
214   }
215
216   public XmlDiff(boolean ignoreWhitespace, IDiffListener diffListener) {
217     this.ignoreWhitespace = ignoreWhitespace;
218     this.diffListener = diffListener;
219   }
220
221   static class XmlDiffException extends RuntimeException JavaDoc {
222     private static final long serialVersionUID = 1L;
223     private Throwable JavaDoc cause;
224
225     public XmlDiffException(Throwable JavaDoc cause) {
226       super(cause.toString());
227       this.cause = cause;
228     }
229
230     public Throwable JavaDoc getCause() {
231       return cause;
232     }
233   }
234
235   public boolean equals(File JavaDoc f1, File JavaDoc f2) {
236     try {
237       return equals(f1.toURL(), f2.toURL());
238     } catch (MalformedURLException JavaDoc e) {
239       throw new XmlDiffException(e);
240     }
241   }
242
243   public boolean equals(URL JavaDoc u1, URL JavaDoc u2) {
244     try {
245       DocumentBuilderFactory JavaDoc dbf = DocumentBuilderFactory.newInstance();
246       dbf.setValidating(false);
247       DocumentBuilder JavaDoc parser = dbf.newDocumentBuilder();
248
249       Document JavaDoc d1 = parser.parse(u1.openStream());
250       Document JavaDoc d2 = parser.parse(u2.openStream());
251
252       if (xslName != null) {
253         d1 = (Document JavaDoc) transform(d1, u1.toExternalForm());
254         d2 = (Document JavaDoc) transform(d2, u2.toExternalForm());
255       }
256
257       boolean b = equals(d1, d2);
258       if (!b && diffListener == null) {
259         System.out.println("\n\nXML Compare failed");
260         System.out.println(u1.toExternalForm());
261         print(d1);
262         System.out.println(u2.toExternalForm());
263         print(d2);
264         System.out.println("\n\n");
265       }
266       return b;
267
268     } catch (FactoryConfigurationError JavaDoc e) {
269       throw new XmlDiffException(e);
270     } catch (ParserConfigurationException JavaDoc e) {
271       throw new XmlDiffException(e);
272     } catch (SAXException JavaDoc e) {
273       throw new XmlDiffException(e);
274     } catch (IOException JavaDoc e) {
275       throw new XmlDiffException(e);
276     } catch (TransformerException JavaDoc e) {
277       throw new XmlDiffException(e);
278     }
279   }
280
281   private Node JavaDoc transform(Node JavaDoc node, String JavaDoc systemId) throws TransformerException JavaDoc {
282     if (xslName == null)
283       return node;
284     String JavaDoc xsl = getClass().getResource(xslName).toExternalForm();
285     TransformerFactory JavaDoc tf = TransformerFactory.newInstance();
286     Transformer JavaDoc tr = tf.newTransformer(new StreamSource JavaDoc(xsl));
287     DOMSource JavaDoc src = new DOMSource JavaDoc(node);
288     src.setSystemId(systemId);
289     DOMResult JavaDoc res = new DOMResult JavaDoc();
290     tr.transform(src, res);
291     return res.getNode();
292   }
293
294   public static void print(Node JavaDoc node) throws TransformerException JavaDoc {
295     TransformerFactory JavaDoc tf = TransformerFactory.newInstance();
296     String JavaDoc xsl = XmlDiff.class.getResource("pretty.xsl").toExternalForm();
297     Source JavaDoc src = new DOMSource JavaDoc(node);
298     Result JavaDoc dest = new StreamResult JavaDoc(System.out);
299     Transformer JavaDoc t = tf.newTransformer(new StreamSource JavaDoc(xsl));
300     t.transform(src, dest);
301   }
302
303   public boolean equals(Document JavaDoc d1, Document JavaDoc d2) {
304     return equals(d1.getDocumentElement(), d2.getDocumentElement());
305   }
306
307   public boolean equals(Element JavaDoc e1, Element JavaDoc e2) {
308     String JavaDoc n1 = e1.getNodeName();
309     String JavaDoc n2 = e2.getNodeName();
310     if (!n1.equals(n2)) {
311       if (diffListener == null)
312         System.out.println("Different elements found " + n1 + " != " + n2);
313       else
314         diffListener.notifyDiffElements(e1, e2);
315       return false;
316     }
317
318     // compate attributes
319
NamedNodeMap JavaDoc atts1 = e1.getAttributes();
320     NamedNodeMap JavaDoc atts2 = e2.getAttributes();
321     if (atts1.getLength() != atts2.getLength()) {
322       if (diffListener == null)
323         System.out.println("Different number of attributes " + n1 + ": " + atts1.getLength()
324             + " != " + n2 + ": " + atts2.getLength());
325       else
326         diffListener.notifyDiffNumberOfAttributes(e1, e2);
327
328       return false;
329     }
330     final int N = atts1.getLength();
331     for (int i = 0; i < N; i++) {
332       Attr JavaDoc a1 = (Attr JavaDoc) atts1.item(i);
333       Attr JavaDoc a2 = (Attr JavaDoc) atts2.getNamedItem(a1.getName());
334       if (a2 == null) {
335         if (diffListener == null) {
336           System.out.println("Attributes differ: " + n1 + "." + a1.getName() + " not found");
337           return false;
338         } else {
339           if (diffListener.notifyDiffAttributeMissing(a1))
340             continue;
341           else
342             return false;
343         }
344        
345       }
346       if (!attrComparator.equals(a1.getValue(), a2.getValue())) {
347         if (diffListener == null) {
348           System.out.println("Attributes differ: " + n1 + "." + a1.getName() + ": " + a1.getValue()
349               + " != " + a2.getValue());
350           return false;
351         } else {
352           if ( !diffListener.notifyDiffAttributes(a1, a2))
353             return false;
354         }
355       }
356     }
357
358     // compare child elements
359
List JavaDoc childs1 = getChildren(e1);
360     List JavaDoc childs2 = getChildren(e2);
361     if (ignoreWhitespace) {
362       removeEmpty(childs1);
363       removeEmpty(childs2);
364     }
365
366     if (childs1.size() != childs2.size()) {
367       if (diffListener == null)
368       System.out.println("Children count of " + n1 + " differ: " + childs1.size() + " != "
369           + childs2.size());
370       else
371         diffListener.notifyDiffNumberOfChildren(e1, e2);
372       
373       return false;
374     }
375     Iterator JavaDoc it1 = childs1.iterator();
376     Iterator JavaDoc it2 = childs2.iterator();
377     for (; it1.hasNext();) {
378       Node JavaDoc c1 = (Node JavaDoc) it1.next();
379       Node JavaDoc c2 = (Node JavaDoc) it2.next();
380       if (!equals(c1, c2))
381         return false;
382     }
383     return true;
384   }
385
386   /**
387    * removes Text nodes containing whitespace
388    */

389   private void removeEmpty(List JavaDoc nodes) {
390     for (Iterator JavaDoc it = nodes.iterator(); it.hasNext();) {
391       Node JavaDoc n = (Node JavaDoc) it.next();
392       if (n.getNodeType() != Node.TEXT_NODE)
393         continue;
394       Text JavaDoc t = (Text JavaDoc) n;
395       String JavaDoc s = t.getData();
396       if (s.trim().length() == 0)
397         it.remove();
398     }
399   }
400
401   public boolean equals(Text JavaDoc t1, Text JavaDoc t2) {
402     String JavaDoc s1 = t1.getData();
403     String JavaDoc s2 = t2.getData();
404     if (ignoreWhitespace) {
405       s1 = s1.trim();
406       s2 = s2.trim();
407     }
408     if (!textComparator.equals(s1, s2)) {
409       if (diffListener == null)
410         System.out.println("Different text elements: \"" + s1 + "\" != \"" + s2 + "\"");
411        else {
412          if ( diffListener.notifyDiffText(t1, t2) )
413            return true;
414        }
415       return false;
416     }
417     return true;
418   }
419
420   public boolean equals(Node JavaDoc n1, Node JavaDoc n2) {
421     // different types can not be equal
422
if (n1.getNodeType() != n2.getNodeType()) {
423       if (diffListener == null)
424         System.out.println("Differnt node type: " + n1.getNodeType() + " != " + n2.getNodeType());
425       else
426         diffListener.notifyDiffNodeType(n1, n2);
427       return false;
428     }
429
430     if (n1.getNodeType() == Node.ELEMENT_NODE)
431       return equals((Element JavaDoc) n1, (Element JavaDoc) n2);
432     if (n1.getNodeType() == Node.TEXT_NODE)
433       return equals((Text JavaDoc) n1, (Text JavaDoc) n2);
434
435     // ignore other node types (comments and such)
436
return true;
437   }
438
439   private static List JavaDoc getChildren(Node JavaDoc parent) {
440     List JavaDoc list = new ArrayList JavaDoc();
441     NodeList JavaDoc children = parent.getChildNodes();
442     for (int i = 0; i < children.getLength(); ++i) {
443       if (children.item(i).getNodeType() == Node.ELEMENT_NODE
444           || children.item(i).getNodeType() == Node.TEXT_NODE)
445         list.add(children.item(i));
446     }
447     return list;
448   }
449
450   public boolean isIgnoreWhitespace() {
451     return ignoreWhitespace;
452   }
453
454   public void setIgnoreWhitespace(boolean b) {
455     ignoreWhitespace = b;
456   }
457
458   /**
459    * name eines XSL Stylesheets fuer Class.getResource(). Dieses
460    * Stylesheet wird vor dem Vergleich der XML Dateien auf beiden
461    * Instanzen ausgefuehrt, z.B. um Knoten zu Filtern, zu Sortieren usw.
462    */

463   public String JavaDoc getXslName() {
464     return xslName;
465   }
466
467   public void setXslName(String JavaDoc xslName) {
468     this.xslName = xslName;
469   }
470
471   public EqualsComparator getAttrComparator() {
472     return attrComparator;
473   }
474
475   public void setAttrComparator(EqualsComparator attrComparator) {
476     this.attrComparator = attrComparator;
477   }
478
479   public EqualsComparator getTextComparator() {
480     return textComparator;
481   }
482
483   public void setTextComparator(EqualsComparator textComparator) {
484     this.textComparator = textComparator;
485   }
486
487   public interface IDiffListener {
488     void notifyDiffElements(Element JavaDoc e1, Element JavaDoc e2);
489     void notifyDiffNumberOfChildren(Element JavaDoc e1, Element JavaDoc e2);
490     void notifyDiffNumberOfAttributes(Element JavaDoc e1, Element JavaDoc e2);
491  
492     /**
493      * @return true, if the difference is to be ignored
494      */

495      boolean notifyDiffAttributeMissing(Attr JavaDoc a1);
496  
497     /**
498      * @return true, if the difference is to be ignored
499      */

500     boolean notifyDiffAttributes(Attr JavaDoc a1, Attr JavaDoc a2);
501
502     /**
503      * @return true, if the difference is to be ignored
504      */

505     boolean notifyDiffText(Text JavaDoc t1, Text JavaDoc t2);
506     
507     void notifyDiffNodeType(Node JavaDoc n1, Node JavaDoc n2);
508   } // IDiffListener
509
}
510
Popular Tags