1 /************************************************************************* 2 * 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * Copyright 2000, 2010 Oracle and/or its affiliates. 6 * 7 * OpenOffice.org - a multi-platform office productivity suite 8 * 9 * This file is part of OpenOffice.org. 10 * 11 * OpenOffice.org is free software: you can redistribute it and/or modify 12 * it under the terms of the GNU Lesser General Public License version 3 13 * only, as published by the Free Software Foundation. 14 * 15 * OpenOffice.org is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 * GNU Lesser General Public License version 3 for more details 19 * (a copy is included in the LICENSE file that accompanied this code). 20 * 21 * You should have received a copy of the GNU Lesser General Public License 22 * version 3 along with OpenOffice.org. If not, see 23 * <http://www.openoffice.org/license.html> 24 * for a copy of the LGPLv3 License. 25 * 26 ************************************************************************/ 27 28 import java.io.FileWriter; 29 import java.io.InputStream; 30 import java.io.FileInputStream; 31 import java.io.BufferedInputStream; 32 import java.io.BufferedWriter; 33 import java.io.IOException; 34 import java.io.FileNotFoundException; 35 import java.io.OutputStream; 36 import java.io.OutputStreamWriter; 37 import java.io.Writer; 38 import java.io.PrintWriter; 39 import java.util.Vector; 40 import java.util.Properties; 41 42 import javax.xml.parsers.DocumentBuilderFactory; 43 import javax.xml.parsers.DocumentBuilder; 44 import javax.xml.parsers.ParserConfigurationException; 45 46 import org.w3c.dom.Node; 47 import org.w3c.dom.Document; 48 import org.w3c.dom.NodeList; 49 import org.xml.sax.SAXException; 50 import org.xml.sax.SAXParseException; 51 /** 52 * This class will diff 2 Xml files. 53 * 54 * @author Stephen Mak 55 */ 56 57 public final class XmlDiff { 58 59 private static final String PROPSFILE = "XmlDiff.properties"; 60 private static final String FILE1 = "XmlDiff.file1"; 61 private static final String FILE2 = "XmlDiff.file2"; 62 private static final String OUTPUT= "XmlDiff.output"; 63 private static final String IGNORE_TAGS= "XmlDiff.tags"; 64 65 private Properties props_ = null; 66 private static PrintWriter writer_ = null; 67 private String[] tags_ = null; 68 private String file1_ = null; 69 private String file2_ = null; 70 71 /** 72 * Constructor. Load the properties file. 73 */ 74 75 public XmlDiff() throws IOException { 76 77 Class c = this.getClass(); 78 InputStream is = c.getResourceAsStream(PROPSFILE); 79 BufferedInputStream bis = new BufferedInputStream(is); 80 props_ = new Properties(); 81 props_.load(bis); 82 bis.close(); 83 84 String file1 = props_.getProperty(FILE1, ""); 85 String file2 = props_.getProperty(FILE2, ""); 86 String tagsString = props_.getProperty(IGNORE_TAGS, ""); 87 String output = props_.getProperty("debug.output", "System.out"); 88 setOutput(output); 89 tags_ = parseTags(tagsString); 90 } 91 92 /** 93 * diff 2 xml, but overwrite the property file's file1/2 setting with 94 * the input argument 95 */ 96 public boolean diff(String file1, String file2) throws IOException { 97 file1_ = file1; 98 file2_ = file2; 99 return diff(); 100 } 101 102 public boolean diff() throws IOException { 103 104 boolean result = false; 105 106 writer_.println("parsing "+ file1_ + "..."); 107 // parse the Xml file 108 Document doc1 = parseXml(file1_); 109 110 writer_.println("parsing "+ file1_ + "..."); 111 Document doc2 = parseXml(file2_); 112 113 if (doc1 != null && doc2 != null) { 114 writer_.println("diffing "+ file1_ + " & " + file2_ + "..."); 115 result = compareNode(doc1, doc2); 116 } 117 return result; 118 } 119 120 private void diffLog(String errMsg, Node node1, Node node2) { 121 122 String node1Str = ""; 123 String node2Str = ""; 124 125 if (node1 != null) { 126 node1Str = "[Type]:" + nodeInfo(node1) + 127 " [Name]:" + node1.getNodeName(); 128 if (node1.getNodeValue() != null) 129 node1Str += " [Value]:" + node1.getNodeValue(); 130 } 131 132 if (node2 != null) { 133 node2Str = "[Type]:" + nodeInfo(node2) + 134 " [Name]:" + node2.getNodeName(); 135 if (node2.getNodeValue() != null) 136 node2Str += " [Value]:" + node2.getNodeValue(); 137 } 138 139 writer_.println(errMsg); 140 writer_.println(" Node1 - " + node1Str); 141 writer_.println(" Node2 - " + node2Str); 142 } 143 144 private String nodeInfo(Node node) { 145 146 String str = null; 147 switch (node.getNodeType()) { 148 149 case Node.ELEMENT_NODE: 150 str = "ELEMENT"; 151 break; 152 case Node.ATTRIBUTE_NODE: 153 str = "ATTRIBUTE"; 154 break; 155 case Node.TEXT_NODE: 156 str = "TEXT"; 157 break; 158 case Node.CDATA_SECTION_NODE: 159 str = "CDATA_SECTION"; 160 break; 161 case Node.ENTITY_REFERENCE_NODE: 162 str = "ENTITY_REFERENCE"; 163 break; 164 case Node.ENTITY_NODE: 165 str = "ENTITY"; 166 break; 167 case Node.PROCESSING_INSTRUCTION_NODE: 168 str = "PROCESSING_INSTRUCTION"; 169 break; 170 case Node.COMMENT_NODE: 171 str = "COMMENT"; 172 break; 173 case Node.DOCUMENT_NODE: 174 str = "DOCUMENT"; 175 break; 176 case Node.DOCUMENT_TYPE_NODE: 177 str = "DOCUMENT_TYPE"; 178 break; 179 case Node.DOCUMENT_FRAGMENT_NODE: 180 str = "DOCUMENT_FRAGMENT"; 181 break; 182 case Node.NOTATION_NODE: 183 str = "NOTATION"; 184 break; 185 } 186 return str; 187 } 188 189 private boolean ignoreTag(String nodeName) { 190 191 192 if (tags_ != null) { 193 for (int i = 0; i < tags_.length; i++) { 194 if (tags_[i].equals(nodeName)) 195 return true; 196 } 197 } 198 return false; 199 } 200 201 // for future use if we want to compare attributes 202 private boolean attributesEqual(Node node1, Node node2) { 203 return true; 204 } 205 206 private boolean compareNode(Node node1, Node node2) { 207 boolean equal = false; 208 209 while (true) { 210 211 if (node1 == null && node2 == null) { 212 equal = true; 213 break; 214 } else if (node1 == null || node2 == null) { 215 diffLog("DIFF: one of the node is null", node1, node2); 216 break; 217 } 218 219 if (node1.getNodeType() != node2.getNodeType()) { 220 diffLog("DIFF: nodetype is different", node1, node2); 221 break; 222 } 223 224 if (node1.getNodeName() == null && node2.getNodeName() == null) { 225 // empty 226 } else if (node1.getNodeName() == null || 227 node2.getNodeName() == null) { 228 diffLog("DIFF: one of the nodeName is null", node1, node2); 229 break; 230 } else if (!node1.getNodeName().equals(node2.getNodeName())) { 231 diffLog("DIFF: nodeName is different", node1, node2); 232 break; 233 } 234 235 if (ignoreTag(node1.getNodeName())) { 236 diffLog("DIFF: Some tag(s) is ignored", node1, node2); 237 equal = true; 238 break; 239 } 240 241 if (node1.getNodeValue() == null && node2.getNodeValue() == null) { 242 // empty 243 } else if (node1.getNodeValue() == null || 244 node2.getNodeValue() == null) { 245 diffLog("DIFF: one of the nodevalue is null", node1, node2); 246 break; 247 } else if (!node1.getNodeValue().equals(node2.getNodeValue())) { 248 diffLog("DIFF: nodeValue is different", node1, node2); 249 break; 250 } 251 252 // try to compare attributes if necessary 253 if (!attributesEqual(node1, node2)) 254 break; 255 256 NodeList node1Children = node1.getChildNodes(); 257 NodeList node2Children = node2.getChildNodes(); 258 259 // number of children have to be the same 260 if (node1Children == null && node2Children == null) { 261 equal = true; 262 break; 263 } 264 265 if (node1Children == null || node2Children == null) { 266 diffLog("DIFF: one node's children is null", node1, node2); 267 break; 268 } 269 270 if (node1Children.getLength() != node2Children.getLength()) { 271 diffLog("DIFF: num of children is different", node1, node2); 272 break; 273 } 274 275 // compare all the childrens 276 equal = true; 277 278 for (int i = 0; i < node1Children.getLength(); i++) { 279 if (!compareNode(node1Children.item(i), 280 node2Children.item(i))) { 281 equal = false; 282 break; 283 } 284 } 285 break; 286 } 287 288 return equal; 289 } 290 291 private Document parseXml (String filename) throws IOException { 292 293 Document w3cDocument = null; 294 295 FileInputStream fis; 296 297 try { 298 fis = new FileInputStream(filename); 299 } catch (FileNotFoundException ex) { 300 ex.printStackTrace(writer_); 301 writer_.println(ex.getMessage()); 302 return w3cDocument; 303 } 304 305 /** factory for DocumentBuilder objects */ 306 DocumentBuilderFactory factory = null; 307 factory = DocumentBuilderFactory.newInstance(); 308 factory.setNamespaceAware(true); 309 factory.setValidating(false); 310 311 /** DocumentBuilder object */ 312 DocumentBuilder builder = null; 313 314 try { 315 builder = factory.newDocumentBuilder(); 316 } catch (ParserConfigurationException ex) { 317 ex.printStackTrace(writer_); 318 writer_.println(ex.getMessage()); 319 return null; 320 } 321 322 323 builder.setErrorHandler( 324 new org.xml.sax.ErrorHandler() { 325 // ignore fatal errors (an exception is guaranteed) 326 public void fatalError(SAXParseException e) 327 throws SAXException { 328 throw e; 329 } 330 331 public void error(SAXParseException e) 332 throws SAXParseException { 333 // make sure validation error is thrown. 334 throw e; 335 } 336 337 public void warning(SAXParseException e) 338 throws SAXParseException { 339 } 340 } 341 ); 342 343 try { 344 w3cDocument = builder.parse(fis); 345 w3cDocument.getDocumentElement().normalize(); 346 } catch (SAXException ex) { 347 ex.printStackTrace(writer_); 348 writer_.println(ex.getMessage()); 349 return w3cDocument; 350 } 351 352 return w3cDocument; 353 } 354 355 private String [] parseTags(String tagsString) { 356 Vector tagsVector = new Vector(); 357 if (tagsString.length() == 0) 358 return null; 359 360 int start = 0; 361 int end = 0; 362 // break the tag string into a vector of strings by words 363 for (end = tagsString.indexOf(" ", start); 364 end != -1 ; 365 start = end + 1, end = tagsString.indexOf(" ", start)) { 366 tagsVector.add(tagsString.substring(start,end)); 367 } 368 369 tagsVector.add(tagsString.substring(start,tagsString.length())); 370 371 // convert the vector to array 372 String[] tags= new String[tagsVector.size()]; 373 tagsVector.copyInto(tags); 374 375 return tags; 376 } 377 378 379 /** 380 * Set the output to the specified argument. 381 * This method is only used internally to prevent 382 * invalid string parameter. 383 * 384 * @param str output specifier 385 */ 386 private static void setOutput(String str) { 387 388 if (writer_ == null) { 389 390 if (str.equals("System.out")) { 391 392 setOutput(System.out); 393 394 } else if (str.equals("System.err")) { 395 396 setOutput(System.err); 397 398 } else { 399 400 try { 401 402 setOutput(new FileWriter(str)); 403 404 } catch (IOException e) { 405 406 e.printStackTrace(System.err); 407 } 408 } 409 } 410 } 411 412 /** 413 * Set the output to an OutputStream object. 414 * 415 * @param stream OutputStream object 416 */ 417 418 private static void setOutput(OutputStream stream) { 419 420 setOutput(new OutputStreamWriter(stream)); 421 } 422 423 /** 424 * Set the Writer object to manage the output. 425 * 426 * @param w Writer object to write out 427 */ 428 429 private static void setOutput(Writer w) { 430 431 if (writer_ != null) { 432 433 writer_.close(); 434 } 435 436 writer_ = new PrintWriter(new BufferedWriter(w), true); 437 } 438 439 public static void main(String args[]) throws IOException { 440 441 if (args.length != 0 && args.length != 2) { 442 System.out.println("Usage: XmlDiff [<file1> <file2>]."); 443 return; 444 } 445 446 XmlDiff xmldiff = new XmlDiff(); 447 448 boolean same = false; 449 if (args.length == 2) { 450 same = xmldiff.diff(args[0], args[1]); 451 } else { 452 same = xmldiff.diff(); 453 } 454 455 System.out.println("Diff result: " + same); 456 if (same) 457 { 458 System.out.println("XMLDIFFRESULT:PASSED"); 459 } else { 460 System.out.println("XMLDIFFRESULT:FAILED"); 461 } 462 } 463 } 464 465