| 1 package com.tonbeller.tbutils.httpunit; 2 3 import java.io.File ; 4 import java.io.IOException ; 5 import java.net.MalformedURLException ; 6 import java.net.URL ; 7 import java.text.ParseException ; 8 import java.text.SimpleDateFormat ; 9 import java.util.ArrayList ; 10 import java.util.Iterator ; 11 import java.util.List ; 12 import java.util.StringTokenizer ; 13 import java.util.regex.PatternSyntaxException ; 14 15 import javax.xml.parsers.DocumentBuilder ; 16 import javax.xml.parsers.DocumentBuilderFactory ; 17 import javax.xml.parsers.FactoryConfigurationError ; 18 import javax.xml.parsers.ParserConfigurationException ; 19 import javax.xml.transform.Result ; 20 import javax.xml.transform.Source ; 21 import javax.xml.transform.Transformer ; 22 import javax.xml.transform.TransformerException ; 23 import javax.xml.transform.TransformerFactory ; 24 import javax.xml.transform.dom.DOMResult ; 25 import javax.xml.transform.dom.DOMSource ; 26 import javax.xml.transform.stream.StreamResult ; 27 import javax.xml.transform.stream.StreamSource ; 28 29 import org.w3c.dom.Attr ; 30 import org.w3c.dom.Document ; 31 import org.w3c.dom.Element ; 32 import org.w3c.dom.NamedNodeMap ; 33 import org.w3c.dom.Node ; 34 import org.w3c.dom.NodeList ; 35 import org.w3c.dom.Text ; 36 import org.xml.sax.SAXException ; 37 38 45 public class XmlDiff { 46 47 private IDiffListener diffListener = null; 48 49 public interface EqualsComparator { 50 boolean equals(Object o1, Object o2); 51 } 52 53 public static class DefaultEqualsComparator implements EqualsComparator { 54 public boolean equals(Object o1, Object o2) { 55 return o1.equals(o2); 56 } 57 } 58 59 public static class IntegerEqualsComparator implements EqualsComparator { 60 public boolean equals(Object o1, Object o2) { 61 try { 62 String s1 = ((String ) o1).trim(); 63 String s2 = ((String ) o2).trim(); 64 int i1 = Integer.parseInt(s1); 65 int i2 = Integer.parseInt(s2); 66 return i1 == i2; 67 } catch (NumberFormatException e) { 68 return o1.equals(o2); 69 } 70 } 71 } 72 73 public static class IntegerLeadingZerosEqualsComparator implements EqualsComparator { 74 75 private String delimiter; 76 77 public IntegerLeadingZerosEqualsComparator(String delimiter) { 78 this.delimiter = delimiter; 79 } 80 81 public IntegerLeadingZerosEqualsComparator() { 82 this("/"); 83 } 84 85 public boolean equals(Object o1, Object o2) { 88 try { 89 String s1 = ((String ) o1).trim(); 90 String s2 = ((String ) o2).trim(); 91 int i1 = Integer.parseInt(s1); 92 int i2 = Integer.parseInt(s2); 93 return i1 == i2; 94 } catch (NumberFormatException e) { 95 StringTokenizer st1 = new StringTokenizer (((String ) o1).trim(), delimiter); 97 StringTokenizer st2 = new StringTokenizer (((String ) 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 String regex; 114 String replacement; 115 116 public RegularExpressionEqualsComparator(String regex, String replacement) { 117 this.regex = regex; 118 this.replacement = replacement; 119 } 120 121 public boolean equals(Object o1, Object o2) { 122 try { 123 String s1 = ((String ) o1).replaceAll(regex, replacement); 124 String s2 = ((String ) o2).replaceAll(regex, replacement); 125 return s1.equals(s2); 126 } catch (PatternSyntaxException e) { 127 return o1.equals(o2); 128 } 129 } 130 } 131 132 136 public static class DateTimeIgnoreIntegerLeadingZerosEqualsComparator implements EqualsComparator { 137 private String pattern; 138 private String delimiter; 139 140 public DateTimeIgnoreIntegerLeadingZerosEqualsComparator() { 141 this("", "/"); 142 } 143 144 public DateTimeIgnoreIntegerLeadingZerosEqualsComparator(String pattern, String 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 o1, Object o2) { 157 158 String str1 = ((String ) o1).trim(); 160 String str2 = ((String ) o2).trim(); 161 int len = pattern.length(); 162 163 if ((str1.length() >= len) && (str2.length() >= len)) { 164 try { 166 SimpleDateFormat formatter = new SimpleDateFormat (pattern); 167 formatter.parse(str1.substring(0, len)); 168 formatter.parse(str2.substring(0, len)); 169 170 str1 = str1.substring(len); 172 str2 = str2.substring(len); 173 return str1.equals(str2); 174 } catch (ParseException pe) { 175 } 177 } 178 179 try { 182 String s1 = ((String ) o1).trim(); 183 String s2 = ((String ) o2).trim(); 184 int i1 = Integer.parseInt(s1); 185 int i2 = Integer.parseInt(s2); 186 return i1 == i2; 187 } catch (NumberFormatException e) { 188 StringTokenizer st1 = new StringTokenizer (((String ) o1).trim(), delimiter); 190 StringTokenizer st2 = new StringTokenizer (((String ) 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 xslName; 206 207 private EqualsComparator textComparator = new DefaultEqualsComparator(); 209 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 { 222 private static final long serialVersionUID = 1L; 223 private Throwable cause; 224 225 public XmlDiffException(Throwable cause) { 226 super(cause.toString()); 227 this.cause = cause; 228 } 229 230 public Throwable getCause() { 231 return cause; 232 } 233 } 234 235 public boolean equals(File f1, File f2) { 236 try { 237 return equals(f1.toURL(), f2.toURL()); 238 } catch (MalformedURLException e) { 239 throw new XmlDiffException(e); 240 } 241 } 242 243 public boolean equals(URL u1, URL u2) { 244 try { 245 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 246 dbf.setValidating(false); 247 DocumentBuilder parser = dbf.newDocumentBuilder(); 248 249 Document d1 = parser.parse(u1.openStream()); 250 Document d2 = parser.parse(u2.openStream()); 251 252 if (xslName != null) { 253 d1 = (Document ) transform(d1, u1.toExternalForm()); 254 d2 = (Document ) 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 e) { 269 throw new XmlDiffException(e); 270 } catch (ParserConfigurationException e) { 271 throw new XmlDiffException(e); 272 } catch (SAXException e) { 273 throw new XmlDiffException(e); 274 } catch (IOException e) { 275 throw new XmlDiffException(e); 276 } catch (TransformerException e) { 277 throw new XmlDiffException(e); 278 } 279 } 280 281 private Node transform(Node node, String systemId) throws TransformerException { 282 if (xslName == null) 283 return node; 284 String xsl = getClass().getResource(xslName).toExternalForm(); 285 TransformerFactory tf = TransformerFactory.newInstance(); 286 Transformer tr = tf.newTransformer(new StreamSource (xsl)); 287 DOMSource src = new DOMSource (node); 288 src.setSystemId(systemId); 289 DOMResult res = new DOMResult (); 290 tr.transform(src, res); 291 return res.getNode(); 292 } 293 294 public static void print(Node node) throws TransformerException { 295 TransformerFactory tf = TransformerFactory.newInstance(); 296 String xsl = XmlDiff.class.getResource("pretty.xsl").toExternalForm(); 297 Source src = new DOMSource (node); 298 Result dest = new StreamResult (System.out); 299 Transformer t = tf.newTransformer(new StreamSource (xsl)); 300 t.transform(src, dest); 301 } 302 303 public boolean equals(Document d1, Document d2) { 304 return equals(d1.getDocumentElement(), d2.getDocumentElement()); 305 } 306 307 public boolean equals(Element e1, Element e2) { 308 String n1 = e1.getNodeName(); 309 String 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 NamedNodeMap atts1 = e1.getAttributes(); 320 NamedNodeMap 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 a1 = (Attr ) atts1.item(i); 333 Attr a2 = (Attr ) 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 List childs1 = getChildren(e1); 360 List 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 it1 = childs1.iterator(); 376 Iterator it2 = childs2.iterator(); 377 for (; it1.hasNext();) { 378 Node c1 = (Node ) it1.next(); 379 Node c2 = (Node ) it2.next(); 380 if (!equals(c1, c2)) 381 return false; 382 } 383 return true; 384 } 385 386 389 private void removeEmpty(List nodes) { 390 for (Iterator it = nodes.iterator(); it.hasNext();) { 391 Node n = (Node ) it.next(); 392 if (n.getNodeType() != Node.TEXT_NODE) 393 continue; 394 Text t = (Text ) n; 395 String s = t.getData(); 396 if (s.trim().length() == 0) 397 it.remove(); 398 } 399 } 400 401 public boolean equals(Text t1, Text t2) { 402 String s1 = t1.getData(); 403 String 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 n1, Node n2) { 421 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 ) n1, (Element ) n2); 432 if (n1.getNodeType() == Node.TEXT_NODE) 433 return equals((Text ) n1, (Text ) n2); 434 435 return true; 437 } 438 439 private static List getChildren(Node parent) { 440 List list = new ArrayList (); 441 NodeList 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 463 public String getXslName() { 464 return xslName; 465 } 466 467 public void setXslName(String 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 e1, Element e2); 489 void notifyDiffNumberOfChildren(Element e1, Element e2); 490 void notifyDiffNumberOfAttributes(Element e1, Element e2); 491 492 495 boolean notifyDiffAttributeMissing(Attr a1); 496 497 500 boolean notifyDiffAttributes(Attr a1, Attr a2); 501 502 505 boolean notifyDiffText(Text t1, Text t2); 506 507 void notifyDiffNodeType(Node n1, Node n2); 508 } } 510 | Popular Tags |