blob: c4b5457704747b20335733c14772b272ce854963 [file] [log] [blame]
/**************************************************************
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*************************************************************/
import java.io.FileWriter;
import java.io.InputStream;
import java.io.FileInputStream;
import java.io.BufferedInputStream;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.FileNotFoundException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.io.PrintWriter;
import java.util.Vector;
import java.util.Properties;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Node;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
/**
* This class will diff 2 Xml files.
*
* @author Stephen Mak
*/
public final class XmlDiff {
private static final String PROPSFILE = "XmlDiff.properties";
private static final String FILE1 = "XmlDiff.file1";
private static final String FILE2 = "XmlDiff.file2";
private static final String OUTPUT= "XmlDiff.output";
private static final String IGNORE_TAGS= "XmlDiff.tags";
private Properties props_ = null;
private static PrintWriter writer_ = null;
private String[] tags_ = null;
private String file1_ = null;
private String file2_ = null;
/**
* Constructor. Load the properties file.
*/
public XmlDiff() throws IOException {
Class c = this.getClass();
InputStream is = c.getResourceAsStream(PROPSFILE);
BufferedInputStream bis = new BufferedInputStream(is);
props_ = new Properties();
props_.load(bis);
bis.close();
String file1 = props_.getProperty(FILE1, "");
String file2 = props_.getProperty(FILE2, "");
String tagsString = props_.getProperty(IGNORE_TAGS, "");
String output = props_.getProperty("debug.output", "System.out");
setOutput(output);
tags_ = parseTags(tagsString);
}
/**
* diff 2 xml, but overwrite the property file's file1/2 setting with
* the input argument
*/
public boolean diff(String file1, String file2) throws IOException {
file1_ = file1;
file2_ = file2;
return diff();
}
public boolean diff() throws IOException {
boolean result = false;
writer_.println("parsing "+ file1_ + "...");
// parse the Xml file
Document doc1 = parseXml(file1_);
writer_.println("parsing "+ file1_ + "...");
Document doc2 = parseXml(file2_);
if (doc1 != null && doc2 != null) {
writer_.println("diffing "+ file1_ + " & " + file2_ + "...");
result = compareNode(doc1, doc2);
}
return result;
}
private void diffLog(String errMsg, Node node1, Node node2) {
String node1Str = "";
String node2Str = "";
if (node1 != null) {
node1Str = "[Type]:" + nodeInfo(node1) +
" [Name]:" + node1.getNodeName();
if (node1.getNodeValue() != null)
node1Str += " [Value]:" + node1.getNodeValue();
}
if (node2 != null) {
node2Str = "[Type]:" + nodeInfo(node2) +
" [Name]:" + node2.getNodeName();
if (node2.getNodeValue() != null)
node2Str += " [Value]:" + node2.getNodeValue();
}
writer_.println(errMsg);
writer_.println(" Node1 - " + node1Str);
writer_.println(" Node2 - " + node2Str);
}
private String nodeInfo(Node node) {
String str = null;
switch (node.getNodeType()) {
case Node.ELEMENT_NODE:
str = "ELEMENT";
break;
case Node.ATTRIBUTE_NODE:
str = "ATTRIBUTE";
break;
case Node.TEXT_NODE:
str = "TEXT";
break;
case Node.CDATA_SECTION_NODE:
str = "CDATA_SECTION";
break;
case Node.ENTITY_REFERENCE_NODE:
str = "ENTITY_REFERENCE";
break;
case Node.ENTITY_NODE:
str = "ENTITY";
break;
case Node.PROCESSING_INSTRUCTION_NODE:
str = "PROCESSING_INSTRUCTION";
break;
case Node.COMMENT_NODE:
str = "COMMENT";
break;
case Node.DOCUMENT_NODE:
str = "DOCUMENT";
break;
case Node.DOCUMENT_TYPE_NODE:
str = "DOCUMENT_TYPE";
break;
case Node.DOCUMENT_FRAGMENT_NODE:
str = "DOCUMENT_FRAGMENT";
break;
case Node.NOTATION_NODE:
str = "NOTATION";
break;
}
return str;
}
private boolean ignoreTag(String nodeName) {
if (tags_ != null) {
for (int i = 0; i < tags_.length; i++) {
if (tags_[i].equals(nodeName))
return true;
}
}
return false;
}
// for future use if we want to compare attributes
private boolean attributesEqual(Node node1, Node node2) {
return true;
}
private boolean compareNode(Node node1, Node node2) {
boolean equal = false;
while (true) {
if (node1 == null && node2 == null) {
equal = true;
break;
} else if (node1 == null || node2 == null) {
diffLog("DIFF: one of the node is null", node1, node2);
break;
}
if (node1.getNodeType() != node2.getNodeType()) {
diffLog("DIFF: nodetype is different", node1, node2);
break;
}
if (node1.getNodeName() == null && node2.getNodeName() == null) {
// empty
} else if (node1.getNodeName() == null ||
node2.getNodeName() == null) {
diffLog("DIFF: one of the nodeName is null", node1, node2);
break;
} else if (!node1.getNodeName().equals(node2.getNodeName())) {
diffLog("DIFF: nodeName is different", node1, node2);
break;
}
if (ignoreTag(node1.getNodeName())) {
diffLog("DIFF: Some tag(s) is ignored", node1, node2);
equal = true;
break;
}
if (node1.getNodeValue() == null && node2.getNodeValue() == null) {
// empty
} else if (node1.getNodeValue() == null ||
node2.getNodeValue() == null) {
diffLog("DIFF: one of the nodevalue is null", node1, node2);
break;
} else if (!node1.getNodeValue().equals(node2.getNodeValue())) {
diffLog("DIFF: nodeValue is different", node1, node2);
break;
}
// try to compare attributes if necessary
if (!attributesEqual(node1, node2))
break;
NodeList node1Children = node1.getChildNodes();
NodeList node2Children = node2.getChildNodes();
// number of children have to be the same
if (node1Children == null && node2Children == null) {
equal = true;
break;
}
if (node1Children == null || node2Children == null) {
diffLog("DIFF: one node's children is null", node1, node2);
break;
}
if (node1Children.getLength() != node2Children.getLength()) {
diffLog("DIFF: num of children is different", node1, node2);
break;
}
// compare all the childrens
equal = true;
for (int i = 0; i < node1Children.getLength(); i++) {
if (!compareNode(node1Children.item(i),
node2Children.item(i))) {
equal = false;
break;
}
}
break;
}
return equal;
}
private Document parseXml (String filename) throws IOException {
Document w3cDocument = null;
FileInputStream fis;
try {
fis = new FileInputStream(filename);
} catch (FileNotFoundException ex) {
ex.printStackTrace(writer_);
writer_.println(ex.getMessage());
return w3cDocument;
}
/** factory for DocumentBuilder objects */
DocumentBuilderFactory factory = null;
factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
factory.setValidating(false);
/** DocumentBuilder object */
DocumentBuilder builder = null;
try {
builder = factory.newDocumentBuilder();
} catch (ParserConfigurationException ex) {
ex.printStackTrace(writer_);
writer_.println(ex.getMessage());
return null;
}
builder.setErrorHandler(
new org.xml.sax.ErrorHandler() {
// ignore fatal errors (an exception is guaranteed)
public void fatalError(SAXParseException e)
throws SAXException {
throw e;
}
public void error(SAXParseException e)
throws SAXParseException {
// make sure validation error is thrown.
throw e;
}
public void warning(SAXParseException e)
throws SAXParseException {
}
}
);
try {
w3cDocument = builder.parse(fis);
w3cDocument.getDocumentElement().normalize();
} catch (SAXException ex) {
ex.printStackTrace(writer_);
writer_.println(ex.getMessage());
return w3cDocument;
}
return w3cDocument;
}
private String [] parseTags(String tagsString) {
Vector tagsVector = new Vector();
if (tagsString.length() == 0)
return null;
int start = 0;
int end = 0;
// break the tag string into a vector of strings by words
for (end = tagsString.indexOf(" ", start);
end != -1 ;
start = end + 1, end = tagsString.indexOf(" ", start)) {
tagsVector.add(tagsString.substring(start,end));
}
tagsVector.add(tagsString.substring(start,tagsString.length()));
// convert the vector to array
String[] tags= new String[tagsVector.size()];
tagsVector.copyInto(tags);
return tags;
}
/**
* Set the output to the specified argument.
* This method is only used internally to prevent
* invalid string parameter.
*
* @param str output specifier
*/
private static void setOutput(String str) {
if (writer_ == null) {
if (str.equals("System.out")) {
setOutput(System.out);
} else if (str.equals("System.err")) {
setOutput(System.err);
} else {
try {
setOutput(new FileWriter(str));
} catch (IOException e) {
e.printStackTrace(System.err);
}
}
}
}
/**
* Set the output to an OutputStream object.
*
* @param stream OutputStream object
*/
private static void setOutput(OutputStream stream) {
setOutput(new OutputStreamWriter(stream));
}
/**
* Set the Writer object to manage the output.
*
* @param w Writer object to write out
*/
private static void setOutput(Writer w) {
if (writer_ != null) {
writer_.close();
}
writer_ = new PrintWriter(new BufferedWriter(w), true);
}
public static void main(String args[]) throws IOException {
if (args.length != 0 && args.length != 2) {
System.out.println("Usage: XmlDiff [<file1> <file2>].");
return;
}
XmlDiff xmldiff = new XmlDiff();
boolean same = false;
if (args.length == 2) {
same = xmldiff.diff(args[0], args[1]);
} else {
same = xmldiff.diff();
}
System.out.println("Diff result: " + same);
if (same)
{
System.out.println("XMLDIFFRESULT:PASSED");
} else {
System.out.println("XMLDIFFRESULT:FAILED");
}
}
}