DOM Class

package javaxt.xml;

import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import org.w3c.dom.*;
import javax.xml.parsers.*;

//imports used to transform xml
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

//******************************************************************************
//**  DOM Utilities
//******************************************************************************
/**
 *   Provides basic utilities to simplify loading and parsing xml
 *
 ******************************************************************************/

public class DOM {

  //**************************************************************************
  //** Private Constructor
  //**************************************************************************
  /**  Defeats Instantiation */

    private DOM() {
    }


  //**************************************************************************
  //** createDocument
  //**************************************************************************
  /**  Used to create a DOM document from a URL. */

    public static Document createDocument(java.net.URL url){
        return new javaxt.http.Request(url).getResponse().getXML();
    }


  //**************************************************************************
  //** createDocument
  //**************************************************************************
  /** Used to create a DOM document from a java.io.File. Returns null if the
   *  file cannot be serialized to XML.
   */
    public static Document createDocument(java.io.File file){
        if (file.exists()==false) return null;
        try{
            return createDocument(new java.io.FileInputStream(file));
        }
        catch(Exception e){
            //e.printStackTrace();
            return null;
        }
    }


  //**************************************************************************
  //** createDocument
  //**************************************************************************
  /** Used to create a DOM document from a String. Returns null if the string
   *  is invalid.
   */
    public static Document createDocument(String xml){
        if (xml==null) return null;

        xml = xml.trim();
        String encoding = "UTF-8";

        try{
            String xmlHeader = xml.substring(xml.indexOf("<?"), xml.indexOf("?>"));
            if (xmlHeader.contains(" encoding")){
                encoding = xmlHeader.substring(xmlHeader.indexOf(" encoding")+" encoding".length());
                encoding = encoding.substring(encoding.indexOf("=")+1);
                while(encoding.substring(0, 1).equals(" ")){
                    encoding = encoding.substring(1);
                }

                String firstChar = encoding.substring(0, 1);
                if (firstChar.equals("\"") || firstChar.equals("'")){
                    encoding = encoding.substring(1);
                    encoding = encoding.substring(0, encoding.indexOf(firstChar)).trim();
                }
                else{
                    encoding = encoding.substring(0, encoding.indexOf(" ")).trim();
                }

            }
        }
        catch(Exception e){}

        return createDocument(xml, encoding);
    }



    public static Document createDocument(String xml, String charsetName){
        xml = xml.trim();
        try{
            return createDocument(new ByteArrayInputStream(xml.getBytes(charsetName)));
        }
        catch(Exception e){
            //e.printStackTrace();
            return createDocument(new ByteArrayInputStream(xml.getBytes()));
        }
    }


  //**************************************************************************
  //** createDocument
  //**************************************************************************
  /**  Used to create a DOM document from an InputStream. */

    public static Document createDocument(InputStream is){
        try{
            DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = builderFactory.newDocumentBuilder();
            return builder.parse(is);
        }
        catch(Exception e){
            //e.printStackTrace();
            return null;
        }
    }



  //**************************************************************************
  //** getOuterNode
  //**************************************************************************
  /** Returns the outer node for a given xml document.
   *  @param xml A org.w3c.dom.Document
   */
    public static Node getOuterNode(Document xml){
        if (xml==null) return null;
        NodeList OuterNodes = xml.getChildNodes();
        for (int i=0; i<OuterNodes.getLength(); i++ ) {
             if (OuterNodes.item(i).getNodeType() == 1){
                 return OuterNodes.item(i);
             }
        }
        return null;
    }


  //**************************************************************************
  //** equals
  //**************************************************************************
  /** Used to compare two XML documents by performing a raw string comparison.
   */
    public static boolean equals(Document xml, Document xml2){
        if (xml==null && xml2==null) return true;
        if (xml!=null && xml2==null) return false;
        if (xml==null && xml2!=null) return false;
        return getText(xml).equals(getText(xml2));
    }


  //**************************************************************************
  //** getText
  //**************************************************************************
  /** Converts a DOM Document to a String
   *  @param xml A org.w3c.dom.Document
   */
    public static String getText(Document xml){

        try{
            TransformerFactory factory = TransformerFactory.newInstance();
            Transformer transformer = factory.newTransformer();
            DOMSource source = new DOMSource(xml);
            ByteArrayOutputStream bas = new ByteArrayOutputStream();
            StreamResult result = new StreamResult(bas);
            transformer.transform(source, result);
            String encoding = xml.getXmlEncoding();
            if (encoding!=null) return bas.toString(encoding);
            else return bas.toString();
        }
        catch (Exception e){
            //System.out.println(e.toString());
            return "";
        }
    }


  //**************************************************************************
  //** getText
  //**************************************************************************
  /** Converts a NodeList to a String */

    public static String getText(NodeList nodeList){
        StringBuffer ret = new StringBuffer();
        for (int i=0; i<nodeList.getLength(); i++ ) {
            ret.append(getText(nodeList.item(i)));
        }
        return ret.toString();
    }


  //**************************************************************************
  //** getText
  //**************************************************************************
  /** Converts a Node to a String */

    public static String getText(Node node){
         StringBuffer ret = new StringBuffer();
         if (node.getNodeType()==1){

             if (hasChildren(node)){
                 ret.append(getNodeValue(node));
             }
             else{
                 ret.append("<" + node.getNodeName() + getAttributes(node) + ">");
                 ret.append(getNodeValue(node));
                 ret.append("</" + node.getNodeName() + ">");
             }
         }
         return ret.toString();
    }


  //**************************************************************************
  //** getAttributes
  //**************************************************************************
  /** Used to retrieve all of the attributes for a given node.   */

    private static String getAttributes(Node node){
        if (node==null) return "";
        NamedNodeMap attr = node.getAttributes();
        String Attributes = "";
        if (attr!=null){
            for (int j=0; j<attr.getLength(); j++){
                 String name = attr.item(j).getNodeName();
                 String value = attr.item(j).getTextContent();
                 if (value==null) value = attr.item(j).getNodeValue();
                 if (value==null) value = "";
                 //System.out.println(name + "=" + attr.item(j).getNodeValue());
                 Attributes += " " + name + "=\"" + value + "\"";
            }
        }
        return Attributes;
    }


  //**************************************************************************
  //** hasChildren
  //**************************************************************************
  /** Used to determine whether a given node has any children. Differs from the
   *  native DOM implementation in that this function only considers child
   *  nodes that have a node type value equal to 1.
   */
    public static boolean hasChildren(Node node){

        NodeList nodeList = node.getChildNodes();
        for (int i=0; i<nodeList.getLength(); i++ ) {
            if (nodeList.item(i).getNodeType()==1){
                return true;
            }
        }
        return false;
    }


  //**************************************************************************
  //** getAttributeValue
  //**************************************************************************
  /** Used to return the value of a given node attribute. The search is case
   *  insensitive. If no match is found, returns an empty string.
   */
    public static String getAttributeValue(NamedNodeMap attrCollection, String attrName){

        if (attrCollection!=null){
            for (int i=0; i < attrCollection.getLength(); i++ ) {
                Node node = attrCollection.item(i);
                if (node.getNodeName().equalsIgnoreCase(attrName)) {
                    return node.getNodeValue();
                }
            }
        }
        return "";
    }


  //**************************************************************************
  //** getAttributeValue
  //**************************************************************************
  /** Used to return the value of a given node attribute. The search is case
   *  insensitive. If no match is found, returns an empty string.
   */
    public static String getAttributeValue(Node node, String attrName){
        return getAttributeValue(node.getAttributes(), attrName);
    }


  //**************************************************************************
  //** setAttributeValue
  //**************************************************************************
  /** Used to create or update an attribute.
   */
    public static void setAttributeValue(Node node, String attrName, String attrValue){

        if (node==null) return;

        try{
            ((Element)node).setAttribute(attrName, attrValue);
        }
        catch(Exception e){

          //Apparently some nodes can't be cast to an Element so we can try
          //to update the node's attribute collection.
            try{

                NamedNodeMap attrCollection = node.getAttributes();
                for (int i=0; i<attrCollection.getLength(); i++ ) {
                    org.w3c.dom.Node attribute = attrCollection.item(i);
                    if (attribute.getNodeName().equals(attrName)) {
                        attribute.setNodeValue(attrValue);
                        return;
                    }
                }

                //TODO: If we're still here, create new attribute
            }
            catch(Exception ex){
            }
        }
    }



  //**************************************************************************
  //** getNodeValue
  //**************************************************************************
  /** Returns the value of a given node as text. If the node has children, the
   *  method will return an xml fragment which will include the input node as
   *  the outer node. This is a legacy feature which should be deprecated over
   *  time.
   */
    public static String getNodeValue(Node node){

        String nodeValue = "";

        if (hasChildren(node)) {

            StringBuffer xmlTree = new StringBuffer();
            traverse(node, xmlTree);
            nodeValue = xmlTree.toString();

        }
        else{
            nodeValue = node.getTextContent();
        }

        if (nodeValue == null){
            return "";
        }
        else{
            return nodeValue;
        }
    }

    private static void traverse(Node tree, StringBuffer xmlTree){
        if (tree.getNodeType()==1){
            String Attributes = getAttributes(tree);
            xmlTree.append("<" + tree.getNodeName() + Attributes + ">");
            if (hasChildren(tree)) {

                NodeList xmlNodeList = tree.getChildNodes();
                for (int i=0; i<xmlNodeList.getLength(); i++){
                    traverse(xmlNodeList.item(i), xmlTree);
                }

            }
            else{

                String nodeValue = tree.getTextContent();
                if (nodeValue == null){
                    nodeValue = "";
                }

                xmlTree.append(nodeValue);
            }

            xmlTree.append("</" + tree.getNodeName() + ">");
        }
    }


  //**************************************************************************
  //** getDocumentAttributes
  //**************************************************************************
  /**
   *  @param xml A org.w3c.dom.Document
   */
    public static NamedNodeMap getDocumentAttributes(Document xml){
        NodeList Definitions = xml.getChildNodes();
        for (int i=0; i<Definitions.getLength(); i++ ) {
             if (Definitions.item(i).getNodeType() == 1){
                 return Definitions.item(i).getAttributes();
             }
        }
        return null;
    }


  //**************************************************************************
  //** getTargetNameSpace
  //**************************************************************************
  /** Returns the "targetNamespace" for a given xml document.
   *  @param xml A org.w3c.dom.Document
   */
    public static String getTargetNameSpace(Document xml){
        NamedNodeMap attr = getDocumentAttributes(xml);
        return getAttributeValue(attr,"targetNamespace");
    }


  //**************************************************************************
  //** getNameSpaces
  //**************************************************************************
  /** Returns a hashmap with all the namespaces found in a given xml document.
   *  The hashmap key is the namespace prefix and the corresponding value is
   *  the namespace url.
   *
   *  @param xml A org.w3c.dom.Document
   */
    public static java.util.HashMap<String, String> getNameSpaces(Document xml){
        java.util.HashMap<String, String> namespaces = new java.util.HashMap<String, String>();
        getNameSpaces(getOuterNode(xml), namespaces);
        return namespaces;
    }

    private static void getNameSpaces(Node node, java.util.HashMap<String, String> namespaces){
        if (node.getNodeType()==1){

            NamedNodeMap attr = node.getAttributes();
            if (attr!=null){
                for (int j=0; j<attr.getLength(); j++){
                     String name = attr.item(j).getNodeName();
                     String value = attr.item(j).getTextContent();
                     if (name.startsWith("xmlns:")){
                         name = name.substring(6);
                         namespaces.put(name, value);
                     }
                }
            }


            if (hasChildren(node)) {
                NodeList childNodes = node.getChildNodes();
                for (int i=0; i<childNodes.getLength(); i++){
                    getNameSpaces(childNodes.item(i), namespaces);
                }
            }

        }
    }



  //**************************************************************************
  //** getElementsByTagName
  //**************************************************************************
  /** Returns an array of nodes that match a given tagName (node name). The
   *  results will include all nodes that match, regardless of namespace. To
   *  narrow the results to a specific namespace, simply include the namespace
   *  prefix in the tag name (e.g. "t:Contact").
   */
    public static org.w3c.dom.Node[] getElementsByTagName(String tagName, Document xml){
        return getElementsByTagName(tagName, getOuterNode(xml));
    }

  //**************************************************************************
  //** getElementsByTagName
  //**************************************************************************
  /** Returns an array of nodes that match a given tagName (node name). The
   *  results will include all nodes that match, regardless of namespace. To
   *  narrow the results to a specific namespace, simply include the namespace
   *  prefix in the tag name (e.g. "t:Contact"). Returns an empty array if
   *  no nodes are found.
   */
    public static org.w3c.dom.Node[] getElementsByTagName(String tagName, Node node){
        java.util.ArrayList<Node> nodes = new java.util.ArrayList<Node>();
        getElementsByTagName(tagName, node, nodes);
        return nodes.toArray(new org.w3c.dom.Node[nodes.size()]);
    }

    private static void getElementsByTagName(String tagName, Node node, java.util.ArrayList<Node> nodes){
        if (node!=null && node.getNodeType()==1){

            String nodeName = node.getNodeName().trim();
            if (nodeName.contains(":") && !tagName.contains(":")){
                nodeName = nodeName.substring(nodeName.indexOf(":")+1);
            }

            if (nodeName.equalsIgnoreCase(tagName)){
                nodes.add(node);
            }

            if (hasChildren(node)) {
                NodeList childNodes = node.getChildNodes();
                for (int i=0; i<childNodes.getLength(); i++){
                    getElementsByTagName(tagName, childNodes.item(i), nodes);
                }
            }

        }
    }


  /** Converts a NodeList into an array to simplify nested loops. */
    public static Node[] getNodes(NodeList nodeList){
        java.util.ArrayList<Node> nodes = new java.util.ArrayList<Node>();
        for (int i=0; i<nodeList.getLength(); i++){
            Node node = nodeList.item(i);
            if (node.getNodeType()==1) nodes.add(node);
        }
        return nodes.toArray(new Node[nodes.size()]);
    }



    public final static String ESCAPE_CHARS = "<>&\"\'";
    public final static java.util.List<String> ESCAPE_STRINGS =
    java.util.Collections.unmodifiableList(java.util.Arrays.asList(new String[] {
      "<"
    , ">"
    , "&"
    , """
    , "'"
    }));

    private static String UNICODE_LOW =  "" + ((char)0x20); //space
    private static String UNICODE_HIGH = "" + ((char)0x7f);


  //**************************************************************************
  //** escapeXml
  //**************************************************************************
  /** Used to encode text data for use in an XML tag or attribute.
   */
    public static String escapeXml(String content) {
        String result = content;

        if ((content != null) && (content.length() > 0)) {
          boolean modified = false;
          StringBuilder stringBuilder = new StringBuilder(content.length());
          for (int i = 0, count = content.length(); i < count; ++i) {
            String character = content.substring(i, i + 1);
            int pos = ESCAPE_CHARS.indexOf(character);
            if (pos > -1) {
              stringBuilder.append(ESCAPE_STRINGS.get(pos));
              modified = true;
            }
            else {
              if (    (character.compareTo(UNICODE_LOW) > -1)
                   && (character.compareTo(UNICODE_HIGH) < 1)
                 ) {
                stringBuilder.append(character);
              }
              else {
                stringBuilder.append("&#" + ((int)character.charAt(0)) + ";");
                modified = true;
              }
            }
          }
          if (modified) {
            result = stringBuilder.toString();
          }
        }

        return result;
    }

}