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