import javax.xml.parsers.DocumentBuilder; 
import javax.xml.parsers.DocumentBuilderFactory;  
import javax.xml.parsers.FactoryConfigurationError;  
import javax.xml.parsers.ParserConfigurationException;

import org.xml.sax.SAXException;  
import org.xml.sax.SAXParseException;

import java.io.File;
import java.io.IOException;

import org.w3c.dom.Document;
import org.w3c.dom.DOMException;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.w3c.dom.Node;

/**
 * A quick and dirty class to compare XML files.  Only handles nodes
 * of type NODE and TEXT (no ATTRIBUTES right now).
 *
 * Usage:
 *   From Java
 *     Call "compareDOMTrees()" and if it returns false, call getReason()
 *     to know where the trees differ.
 *
 *   From a shell
 *     java XMLDiff a.xml b.xml
 *
 * @author Cedric Beust cbeust@bea.com 02-Mar-2000
 *
 */
public class XMLDiff {
  private static String m_reason;

  public static boolean compareNodes(Element e1, Element e2) {
    if (e1.getTagName().equals(e2.getTagName())) {
      return genericCompare(e1, e2);
    }
    else {
      setReason("Different tags:" + e1.getTagName() + " and " + e2.getTagName() + "'");
      return false;
    }
  }

  public static void setReason(String reason) {
    m_reason = reason;
  }

  public static String getReason() {
    return m_reason;
  }

  public static boolean genericCompare(Node n1, Node n2) {
    if (Node.ELEMENT_NODE == n1.getNodeType() &&
        Node.ELEMENT_NODE == n2.getNodeType())
      {
        Element e1 = (Element) n1;
        Element e2 = (Element) n2;
        if (! e1.getTagName().equals(e2.getTagName())) {
          setReason("Different tag name:'" + e1.getTagName() + "' '" + e2.getTagName());
          return false;
        }
    }
    else {
      throw new RuntimeException("Unknown Node type:" + n1.getNodeType());
    }

    NodeList childNodes1 = n1.getChildNodes();
    NodeList childNodes2 = n2.getChildNodes();
    int length1 = childNodes1.getLength();
    int length2 = childNodes2.getLength();
    if (length1 != length2) {
      setReason("Different number of children for:" + n1.getNodeName());
      return false;
    }
    else {
      for (int i = 0; i < length1; i++) {
        Node nn1 = childNodes1.item(i);
        Node nn2 = childNodes2.item(i);
        if (Node.ELEMENT_NODE == nn1.getNodeType() &&
            Node.ELEMENT_NODE == nn2.getNodeType())
        {
          if (! compareNodes((Element) nn1, (Element) nn2)) {
//             System.out.println("Different nodes:" + nn1.getNodeValue()
//                                + " " + nn2.getNodeValue() + " " + n1);
            return false;
          }
        }
        else if (Node.TEXT_NODE == nn1.getNodeType() &&
                 Node.TEXT_NODE == nn2.getNodeType())
        {
          if (! nn1.toString().trim().equals(nn2.toString().trim())) {
            setReason("Different content for element:" + ((Element) n1).getTagName()
                      + ":'" + nn1.toString() + "' and '" + nn2.toString() + "'");
            return false;
          }
        }
        else {
          throw new RuntimeException("Unknown Node type:" + nn1.getNodeType());
        }
      }
    }

    return true;
  }

  public static boolean compareDOMTrees(Document d1, Document d2) {
    if (! d1.getDoctype().equals(d2.getDoctype())) {
      setReason("DOCTYPES differ:\n" + d1.getDoctype() + "\n" + d2.getDoctype());
      return false;
    }
    Element root1 = d1.getDocumentElement();
    Element root2 = d2.getDocumentElement();
    root1.normalize();
    root2.normalize();
    return genericCompare(root1, root2);
  }

  public static void main(String[] argv) {
    Document document1;
    Document document2;
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    try {
      DocumentBuilder builder = factory.newDocumentBuilder();
      document1 = builder.parse( new File(argv[0]) );
      document2 = builder.parse( new File(argv[1]) );
      if (! compareDOMTrees(document1, document2)) {
        System.out.println(getReason());
      }
      else {
        System.out.println("Identical");
      }
    }
    catch (SAXException sxe) {
      // Error generated during parsing)
      Exception  x = sxe;
      if (sxe.getException() != null)
        x = sxe.getException();
      x.printStackTrace();

    } catch (ParserConfigurationException pce) {
      // Parser with specified options can't be built
      pce.printStackTrace();
    } catch (IOException ioe) {
      // I/O error
      ioe.printStackTrace();
    }
  }
}
