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         if (same)
453         {
454           System.out.println("XMLDIFFRESULT:PASSED");
455         } else {
456           System.out.println("XMLDIFFRESULT:FAILED");
457         }
458     }
459 }
460 
461