import org.apache.xerces.parsers.*;
import org.w3c.dom.*;
import org.w3c.dom.traversal.*;

public class BasicDOMExample {

  public static void main(String[] args) {
    
    if (args.length > 0) {
      String filename = args[0];
      BasicDOMExample exampleDOM = new BasicDOMExample(filename);
    }  
  }

  public BasicDOMExample(String filename) {
    try {
      // Parse in XML file, and construct a document
      DOMParser parser = new DOMParser();

      parser.parse(filename);
      Document document = parser.getDocument();

      // Get the DOMImplementation and check it supports Level 2 Core
      // You can use the Node.isSupported() method to get the same information
      DOMImplementation domImpl = document.getImplementation();
      boolean levelTwoCoreSupport = domImpl.hasFeature("CORE", "2.0");
      if (levelTwoCoreSupport)
        System.out.println("DOM implementation supports DOM Level 2.0 Core");
      else
        System.out.println("DOM implementation doesn't support DOM Level 2.0 Core");

      // We have the document of the DOM tree, from which we can access all the other nodes

      // Let's get the name of the document element
      Element documentElement = document.getDocumentElement();
      System.out.println("");
      System.out.println("Document node name: " + documentElement.getTagName());
      System.out.println("");

      // Iterate over the document element's attributes and output them
      attributeInformation(documentElement);

      // Create an attribute using the document element object
      System.out.println("Adding an attribute to the document element object...");
      documentElement.setAttribute("TESTATTRIBUTE", "TESTING");

      if (documentElement.hasAttribute("TESTATTRIBUTE"))
        System.out.println("Attribute has been successfully added");

      // Output new attribute (one of two ways)
      Attr testAttribute = documentElement.getAttributeNode("TESTATTRIBUTE");
      System.out.println("Element: " + testAttribute.getOwnerElement() + 
                         " has attribute " + testAttribute.getName() + 
                         " with the value " + testAttribute.getValue());
      // Or you can just get the value from the name
      // System.out.println("New attribute, TESTATTRIBUTE has value: " + 
      //                    documentElement.getAttribute("TESTATTRIBUTE"));

      // Create a child element
      // Use the document object to creat the element...
      Element testElement = document.createElement("TESTELEMENT");
      testElement.setAttribute("TESTATTRIBUTE", "In TESTELEMENT");

      // ... then append the element to its parent node
      documentElement.appendChild(testElement);

      // Add a text node to your new child element      
      Text testTextNode = document.createTextNode("Test Text");
      // Just append to the testElement Node...
      testElement.appendChild(testTextNode);

// ***** This doesn't work - gets a null pointer back. What's an ID?
      // Find the INVOICE element by its ID
//      Element childById = document.getElementById("1010");
//      if (childById != null) {
//        System.out.println("Element with ID: " + childById.getTagName();
//        childById.removeChild(testElement);
//      }

      // Get tags by name
      NodeList testTags = document.getElementsByTagName("TESTELEMENT"); 
      System.out.println("TESTELEMENT tags found: " + testTags.getLength());

      // Output all the nodes recursively, down from the document node
      System.out.println("");
      System.out.println("Recursive output of node information:");
      nodeInformation(document);

      // Walk DOM tree, if traversals supported
      if(document.isSupported("Traversal", "2.0")) {
        System.out.println("");
        System.out.println("Walk tree to output LINEITEM node information:");
        TreeWalker walker = ((DocumentTraversal)document).createTreeWalker(
                      documentElement, 
                      NodeFilter.SHOW_ELEMENT, 
                      null,
                      true);

        walker.firstChild();

        do {
          Node currentNode = walker.getCurrentNode();
          System.out.println("Node name: " + currentNode.getNodeName());

          if (currentNode.getNodeType() == Node.ELEMENT_NODE && 
              currentNode.hasAttributes()) {
            // Extract attribute information
            attributeInformation((Element)currentNode);
          }
        } while (walker.nextSibling() != null);

        System.out.println("");
        System.out.println("Iterate over nodes to output element information:");
        NodeIterator iterator = ((DocumentTraversal)document).createNodeIterator(
                      documentElement, 
                      NodeFilter.SHOW_ALL, 
                      null,
                      true);

        Node iteratedNode = iterator.nextNode();
        
        while (iteratedNode != null) {
          System.out.println("Node name: " + iteratedNode.getNodeName());

          if (iteratedNode.getNodeType() == Node.ELEMENT_NODE && 
              iteratedNode.hasAttributes()) {
            // Extract attribute information
            attributeInformation((Element)iteratedNode);
          }

          iteratedNode = iterator.nextNode();
        } 
      }
    } catch (Exception e) {
      e.printStackTrace(System.err);
    }
  }

  public void attributeInformation(Element element) {
    // Iterate over the element's attributes and output them
    NamedNodeMap attributes = element.getAttributes();
    for (int i = 0; i < attributes.getLength(); i++) {
      Attr attribute = (Attr)attributes.item(i);
      System.out.println("Element: " + attribute.getOwnerElement() + 
                         " has attribute " + attribute.getName() + 
                         " with the value " + attribute.getValue());
    }
  }

  public void nodeInformation(Node node) {
    // Is there anything to do?
    if (node == null) {
      return;
    }

    // Output the NodeType and values
    int type = node.getNodeType();
    switch (type) {
      case Node.ATTRIBUTE_NODE: {
        System.out.println("Attribute node: " + ((Attr)node).getName() + 
                           " has value: " + ((Attr)node).getValue());
        break;
      }
      case Node.CDATA_SECTION_NODE: {
        System.out.println("CDATA node: " + node.getNodeName() + 
                           " has value: " + node.getNodeValue());
        break;
      }
      case Node.COMMENT_NODE: {
        System.out.println("Comment node: " + node.getNodeName() + 
                           " has value: " + node.getNodeValue());
        break;
      }
      case Node.DOCUMENT_FRAGMENT_NODE: {
        System.out.println("Document fragment node: " + node.getNodeName());
        outputChildNodes(node);
        break;
      }
      case Node.DOCUMENT_NODE: {
        System.out.println("Document node: " + node.getNodeName());
        outputChildNodes(node);
        break;
      }
      case Node.DOCUMENT_TYPE_NODE: {
        System.out.println("Document type node: " + node.getNodeName());
        break;
      }
      case Node.ELEMENT_NODE: {
        System.out.println("");
        System.out.println("Element node: " + ((Element)node).getTagName());
        outputAttributes(node);
        outputChildNodes(node);
        break;
      }
      case Node.ENTITY_NODE: {
        System.out.println("Entity node: " + node.getNodeName());
        break;
      }
      case Node.ENTITY_REFERENCE_NODE: {
        System.out.println("Entity reference node: " + node.getNodeName());
        break;
      }
      case Node.NOTATION_NODE: {
        System.out.println("Notation node: " + node.getNodeName());
        break;
      }
      case Node.PROCESSING_INSTRUCTION_NODE: {
        System.out.println("Processing instruction node: " + node.getNodeName() + 
                           " has value: " + node.getNodeValue());
        break;
      }
      case Node.TEXT_NODE: {
        System.out.println("Text node: " + node.getNodeName() + 
                           " has value: " + node.getNodeValue());
        break;
      }
    }
  }

  public void outputChildNodes(Node node) {
    // Output all the child nodes from this node recursively, using a NodeList
    NodeList children = node.getChildNodes();

    for (int i = 0; i < children.getLength(); i++) {
      Node child = children.item(i);
      nodeInformation(child);
    }
  }

  public void outputAttributes(Node node) {
    // Output all the attributes from this node recursively, using a NamedNodeMap
    NamedNodeMap attributes = node.getAttributes();

    for (int i = 0; i < attributes.getLength(); i++) {
      Attr attribute = (Attr)attributes.item(i);
      nodeInformation(attribute);
    }
  }
}
