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