Mega Code Archive

 
Categories / Java / XML
 

Handles DOM processing allowing the reading and writing of hierarchical structures as XML files

//package org.gicentre.utils.io; import javax.xml.parsers.*;     // For Document builder factory. import org.xml.sax.*;           // For SAX Exception handling. import org.w3c.dom.*;           // For document object model (DOM). import java.io.*;               // For file handling. import java.util.*;             // For vector structure. //  **************************************************************************************** /** Handles DOM processing allowing the reading and writing of hierarchical structures as  *  XML files. Uses the Document Object Model (DOM) to store the tree of nodes, therefore  *  not suitable for very large structures. For reading very large structures represented as  *  XML, use SAX processing instead.  *  @author Jo Wood, giCentre, City University London.  *  @version 3.1, 18th February, 2011.  */  // ***************************************************************************************** /* This file is part of giCentre utilities library. gicentre.utils is free software: you can   * redistribute it and/or modify it under the terms of the GNU Lesser General Public License  * as published by the Free Software Foundation, either version 3 of the License, or (at your  * option) any later version.  *   * gicentre.utils is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;  * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.    * See the GNU Lesser General Public License for more details.  *   * You should have received a copy of the GNU Lesser General Public License along with this  * source code (see COPYING.LESSER included with this source code). If not, see   * http://www.gnu.org/licenses/.  */    public class DOMProcessor {     // ----------------------- Object Variables -----------------------     private int indent;             // Indent level.       private Document dom;           // Document object model.     private PrintWriter out;        // Output stream.     private Vector<Node> matches;   // List of matching elements.         // ------------------------- Constructors -------------------------         /** Creates a new empty DOM ready for processing.       */     public DOMProcessor()     {           // Do nothing.     }              /** Wraps the given DOM in this processor allowing it to be written       * as an XML file, or appended with new nodes.       * @param dom Document Object Model to use in processor.       */     public DOMProcessor(Document dom)     {           this.dom = dom;     }     /** Reads and the given XML file and constructs a DOM from it.       * @param fileName Name of XML file to read.       */     public DOMProcessor(String fileName)     {            readXML(fileName);             }     /** Reads XML from the given input stream and constructs a DOM from it.       * @param inStream Stream from which to read XML.       */     public DOMProcessor(InputStream inStream)     {            readXML(inStream);             }          // ------------------------- Methods ---------------------------              /** Reports whether we have an empty DOM.       * @return True if DOM is empty.        */     public boolean isEmpty()     {         if (dom == null)         {             return true;         }                  return false;     }          /** Adds a new element to the root of the DOM.       * @param name Name of the new element       * @return New element in the DOM.       */     public Node addElement(String name)     {           if (dom == null)         {             // Create a DocumentBuilder using the DocumentBuilderFactory.             DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();             DocumentBuilder db = null;             indent = 0;             try              {                 db = dbf.newDocumentBuilder();             }             catch (ParserConfigurationException e)              {                 System.err.println("Problem finding an XML parser:\n"+e);                 return null;             }                         dom = db.getDOMImplementation().createDocument(null,name,null);             return dom.getDocumentElement();         }                  return addElement(name,null,dom.getDocumentElement());     }          /** Adds a new element to the given one within the DOM.       * @param name Name of the new element       * @param existingElement Element onto which the new element should be attached.       * @return New element in the DOM.       */     public Node addElement(String name, Node existingElement)     {           return addElement(name,null,existingElement);     }          /** Adds a new element to the given one within the DOM.       * @param name Name of the new element       * @param text Text to attach to element or null if none required.       * @param existingElement Element onto which the new element should be attached.       * @return New element in the DOM.       */     public Node addElement(String name, String text, Node existingElement)     {           // Create the new element node and attach it to existing node.         Node newNode = dom.createElement(name);         existingElement.appendChild(newNode);                  // Add text if given.         if (text != null)         {             Node textNode = dom.createTextNode(text);             newNode.appendChild(textNode);         }         return newNode;     }          /** Renames the given element with the given new name.       * @param existingElement Element to rename.       * @param newName New name to give element.       */     public void renameElement(Node existingElement, String newName)     {         // Create an element with the new name         Node newElement = dom.createElement(newName);                  // Copy the attributes to the new element         NamedNodeMap attrs = existingElement.getAttributes();         for (int i=0; i<attrs.getLength(); i++)         {             Attr attr2 = (Attr)dom.importNode(attrs.item(i), true);             newElement.getAttributes().setNamedItem(attr2);         }                  // Move all the children         while (existingElement.hasChildNodes())          {             newElement.appendChild(existingElement.getFirstChild());         }                  // Replace the old node with the new node         existingElement.getParentNode().replaceChild(newElement, existingElement);     }          /** Adds the given attribute to the given node.       * @param name Attribute name.       * @param value Attribute value.       * @param node Element to attach attribute.       */     public void addAttribute(String name, String value, Node node)     {         if (node.getNodeType() == Node.ELEMENT_NODE)         {             Element element = (Element)node;             element.setAttribute(name,value);         }     }     /** Adds the given comment to the root of the DOM. Note that this method should only        * be called once a root node has been created in the DOM.        * @param comment Comment text.       */     public void addComment(String comment)     {         addComment(comment,dom.getDocumentElement());     }          /** Adds the given comment to the given node.       * @param comment Comment text.       * @param node Element to attach comment.       */     public void addComment(String comment, Node node)     {         node.getParentNode().insertBefore(dom.createComment(comment),node);     }          /** Adds text as the child of the given node.       * @param text Text to add to node.       * @param node Element to attach text.       */     public void addText(String text, Node node)     {           node.appendChild(dom.createTextNode(text));     }     /** Searches the entire DOM for a given element and returns text associated        * with it. If more than one element with the given name exists, multiple       * text values are returned.       * @param elementName Element to search for.       * @return Array of strings associated with all occurrences of        * the given element. Array will be 0 length if none found.       */     public String[] getText(String elementName)     {         return getText(elementName,dom);     }     /** Returns any text associated found in the given node or its children.       * This is equivalent to calling <code>getText(null,node)</code>.        * If more than one element containing text exists, multiple text values        * are returned.       * @param node Node from which to start search.       * @return Array of strings associated with all occurrences of        * text in the node or its children. Array will be 0 length if none found.       */     public String[] getText(Node node)     {         return getText(null,node);         }              /** Searches for a given element and returns text associated with it. If more than one       * element with the given name exists, multiple text values are returned.       * @param elementName Element to search for. If elementName is null, search will be        *                    for all text contained within the given node.       * @param node Node from which to start search.       * @return Array of strings associated with all occurrences of the given element.        *         Array will be 0 length if none found.       */     public String[] getText(String elementName, Node node)     {         matches = new Vector<Node>();         searchText(elementName,node);                  // Convert match vector into an array;         String[] matchArray = new String[matches.size()];         int i=0;         for (Node matchedNode : matches)         {             matchArray[i++] = matchedNode.getNodeValue();         }         matches = null;         return matchArray;      }          /** Searches for a given node and returns text associated with       * it. This version does not recurse to the node's children.       * @param node Node to search.       * @return Text associated with the node, or null if none found.       */     public String getNodeText(Node node)     {         // Look for text in child (text stored in its own node).         NodeList children = node.getChildNodes();                  for (int i=0; i<children.getLength(); i++)         {             Node child = children.item(i);                          if ((child.getNodeType() == Node.CDATA_SECTION_NODE) ||                 (child.getNodeType() == Node.TEXT_NODE))             {                 return(child.getNodeValue());             }         }                  // If we get this far, no text was found.         return null;     }     /** Searches the entire DOM for a given attribute and returns the value associated with it.       * If there is more than one occurrence of the attribute, multiple text values are returned.       * @param attributeName Attribute to search for.       * @return Array of strings associated with all occurrences of the given attribute.       *         Array will be 0 length if none found.       */     public String[] getAttributes(String attributeName)     {         return getAttributes(attributeName,dom);     }          /** Searches the given node and its children for a given attribute and returns the value       * associated with it. If there is more than one occurrence of the attribute, multiple       * text values are returned.       * @param attributeName Attribute to search for.       * @param node Node from which to start search.       * @return Array of strings associated with all occurrences of the given attribute.       *         Array will be 0 length if none found.       */     public String[] getAttributes(String attributeName, Node node)     {         matches = new Vector<Node>();         searchAttributes(attributeName,node);                          // Convert match vector into an array;         String[] matchArray = new String[matches.size()];         int i=0;         for (Node matchedNode : matches)         {             matchArray[i++] = matchedNode.getNodeValue();         }         matches = null;         return matchArray;      }          /** Searches the given node for a given attribute and returns the value associated with it.       * This version does not recurse to children of the given node.       * @param attributeName Attribute to search for.       * @param node Node from which to start search.       * @return Value associated with the attribute, or null if not found.         */     public String getNodeAttribute(String attributeName, Node node)     {         // Only consider document or element nodes.         if ((node.getNodeType() != Node.DOCUMENT_NODE) &&             (node.getNodeType() != Node.ELEMENT_NODE))         {             return null;         }                      // Search attributes associated with the node.         NamedNodeMap attributes = node.getAttributes();         for (int i=0; i<attributes.getLength(); i++)         {             Node attribute = attributes.item(i);             if (attribute.getNodeName().equalsIgnoreCase(attributeName))             {                 return attribute.getNodeValue();             }         }                  // If we get this far, the attribute has not been found.         return null;     }          /** Returns a list of the DOM elements with the given name. This can be       * used to provide the base of sub-trees for searches within nested        * elements.        * @param name Element name to search for.       * @return Array of elements with the given name. Array will be 0 length if none found.       */     public Node[] getElements(String name)     {         return getElements(name,dom);     }              /** Returns a list of the DOM elements with the given name that are        * nested within the given node. This can be used to provide the        * base of sub-trees for searches within nested elements. The order of matched elements       * is depth-first. For breadth-first searches, use <code>getNodeElements</code> and       * recursively search for children of returned nodes.       * @param name Element name to search for.       * @param node Node from which to start search.       * @return Array of elements with the given name. Array will be 0 length if none found.       */     public Node[] getElements(String name, Node node)     {         matches = new Vector<Node>();         searchNode(name,node);                  // Convert match vector into an array;         Node[] matchArray = new Node[matches.size()];         matches.toArray(matchArray);         matches = null;                  return matchArray;      }          /** Returns a DOM element with the given name that is the child of the        * given node. This is a non-recursive method that only looks for immediate       * children. Note that unlike <code>getNodeElements()</code> this method only       * returns the first matched child of the given node.       * @param name Element name to search for.       * @param node Node from which to examine children.       * @return Child node or null if none found.       */     public Node getNodeElement(String name, Node node)     {        // Only consider document or element nodes.         if ((node.getNodeType() != Node.DOCUMENT_NODE) &&             (node.getNodeType() != Node.ELEMENT_NODE))         {             return null;         }                      NodeList children = node.getChildNodes();                  for (int i=0; i<children.getLength(); i++)         {             Node child = children.item(i);                          // Only consider element child nodes.             if (child.getNodeType() == Node.ELEMENT_NODE)             {                 if (child.getNodeName().equalsIgnoreCase(name))                 {                     return child;                 }             }         }                 // If we get this far, no child node was found.         return null;     }          /** Returns the DOM elements with the given name that are the children of the        * given node. This is a non-recursive method that only looks for immediate       * children. Array will be 0 length if none found.       * @param name Element name to search for.       * @param node Node from which to examine children.       * @return Child nodes or empty Node array if none found.       */     public Node[] getNodeElements(String name, Node node)     {         // Only consider document or element nodes.         if ((node.getNodeType() != Node.DOCUMENT_NODE) &&             (node.getNodeType() != Node.ELEMENT_NODE))         {             return new Node[0];         }                  Vector<Node> matchedChildren = new Vector<Node>();                     NodeList children = node.getChildNodes();                 for (int i=0; i<children.getLength(); i++)         {             Node child = children.item(i);                         // Only consider element child nodes.             if (child.getNodeType() == Node.ELEMENT_NODE)             {                 if (child.getNodeName().equalsIgnoreCase(name))                 {                     matchedChildren.add(child);                 }             }         }                  Node[] nodes = new Node[matchedChildren.size()];         matchedChildren.toArray(nodes);         return nodes;     }           /** Reads the given XML file and converts it into a DOM.       * @param fileName Name of XML file to convert.       * @return True if converted successfully.       */     public boolean readXML(String fileName)     {         // Create a DocumentBuilder using the DocumentBuilderFactory.         DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();         DocumentBuilder db = null;         indent = 0;         try          {             db = dbf.newDocumentBuilder();         }         catch (ParserConfigurationException e)          {             System.err.println("Problem finding an XML parser:\n"+e);             return false;         }            // Try to parse the given file and store XML nodes in the DOM.          try          {             dom = db.parse(new File(fileName));         }         catch (SAXException e)          {             System.err.println("Problem parsing document: "+e.getMessage());             dom = db.newDocument();             return false;         }         catch (IOException e)          {             System.err.println("Problem reading "+fileName);             return false;         }         return true;     }               /** Reads the XML from the given input stream and converts it into a DOM.        * @param inStream Input stream containing XML to convert.       * @return True if converted successfully.       */     public boolean readXML(InputStream inStream)     {         // Create a DocumentBuilder using the DocumentBuilderFactory.         DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();         DocumentBuilder db = null;         indent = 0;         try          {             db = dbf.newDocumentBuilder();         }         catch (ParserConfigurationException e)          {             System.err.println("Problem finding an XML parser:\n"+e);             return false;         }            // Try to parse the given file and store XML nodes in the DOM.          try          {             dom = db.parse(inStream);         }         catch (SAXException e)          {             System.err.println("Problem parsing document: "+e.getMessage());             dom = db.newDocument();             return false;         }         catch (IOException e)          {             System.err.println("Problem reading from "+inStream);             return false;         }         return true;     }          /** Displays the DOM stored within this class as an XML file with default        * document settings on standard output.       * @return Always true.       */     public boolean writeXML()     {         return writeXML(null,null,null);     }     /** Displays the DOM stored within this class as an XML file with the given document       * settings on standard output.       * @param version XML version, or null if default ('1.0') is to be used.       * @param encoding XML encoding, or null if encoding is not to be specified.       * @param standalone XML stand-alone status of XML file or null if not to be specified.       * @return Always true.       */     public boolean writeXML(String version, String encoding, Boolean standalone)     {         out = new PrintWriter(System.out);         indent = 0;         outputNodeAsXML(dom,version,encoding,standalone);         // NOTE: Closing the writer to standard output closes stdout itself!         //       So flush output rather than close it.         out.flush();                 return true;     }     /** Converts the DOM stored within this class into an XML file with default document settings.       * @param fileName Name of file to contain the XML.       * @return true if successful XML generation.       */     public boolean writeXML(String fileName)     {         return writeXML(fileName,null,null,null);     }          /** Converts the DOM stored within this class into an XML file with default document settings.       * @param outStream Output stream representing file to contain the XML.       * @return true if successful XML generation.       */     public boolean writeXML(OutputStream outStream)     {         return writeXML(outStream,null,null,null);     }          /** Converts the DOM stored within this class into an XML file with the given document settings.       * @param fileName Name of file to contain the XML.       * @param version XML version, or null if default ('1.0') is to be used.       * @param encoding XML encoding, or null if encoding is not to be specified.       * @param standalone XML stand-alone status of XML file or null if not to be specified.       * @return true if successful XML generation.       */     public boolean writeXML(String fileName, String version, String encoding, Boolean standalone)     {         if (dom == null)         {             System.err.println("Error: No document object model to process.");             return false;         }                  // Open file for output.         try         {                        out = new PrintWriter(new BufferedWriter(new FileWriter(fileName)));         }         catch (IOException e)         {             System.err.println("Error: Problem creating XML file: "+fileName);             return false;         }              // Start recursive output of the whole DOM.         indent = 0;         outputNodeAsXML(dom,version,encoding,standalone);                  // Close output and leave.         out.close();         return true;     }     /** Converts the DOM stored within this class into an XML file with the given document settings.       * @param outStream Output stream representing file to contain the XML.       * @param version XML version, or null if default ('1.0') is to be used.       * @param encoding XML encoding, or null if encoding is not to be specified.       * @param standalone XML stand-alone status of XML file or null if not to be specified.       * @return true if successful XML generation.       */     public boolean writeXML(OutputStream outStream, String version, String encoding, Boolean standalone)     {         if (dom == null)         {             System.err.println("Error: No document object model to process.");             return false;         }                  // Open file for output.         out = new PrintWriter(outStream);                     // Start recursive output of the whole DOM.         indent = 0;         outputNodeAsXML(dom,version,encoding,standalone);                  // Close output and leave.         out.close();         return true;     }          // ---------------------- Private Methods --------------------------          /** Searches for a given element in the given node and updates list       * of text within matched elements. Recursively searches for sub-nodes       * of the given one.       * @param element Element to search for. If null, all elements searched.       * @param node Node to start search from.       */     private void searchText(String element, Node node)     {         // Only consider document or element nodes.         if ((node.getNodeType() != Node.DOCUMENT_NODE) &&             (node.getNodeType() != Node.ELEMENT_NODE))         {             return;         }                      if ((element == null) || (node.getNodeName().equalsIgnoreCase(element)))         {             // Match found so look for text in children.             NodeList children = node.getChildNodes();                          for (int i=0; i<children.getLength(); i++)             {                 Node child = children.item(i);                                  if ((child.getNodeType() == Node.CDATA_SECTION_NODE) ||                     (child.getNodeType() == Node.TEXT_NODE))                 {                     if (child.getNodeValue().trim().length() > 0)                     {                         //matches.add(child.getNodeValue());                         matches.add(child);                     }                 }             }         }                  if ((node.getNodeType() == Node.DOCUMENT_NODE) ||             (node.getNodeType() == Node.ELEMENT_NODE))         {             // Search child nodes.             NodeList children = node.getChildNodes();                          for (int i=0; i<children.getLength(); i++)             {                 searchText(element,children.item(i));             }         }     }          /** Searches for a given attribute in the given node and updates list       * of attribute values. Recursively searches for sub-nodes of the given one.       * @param element Element to search for.       * @param node Node to start search from.       */     private void searchAttributes(String element, Node node)     {         // Only consider document or element nodes.         if ((node.getNodeType() != Node.DOCUMENT_NODE) &&             (node.getNodeType() != Node.ELEMENT_NODE))         {             return;         }                      // Search attributes associated with current node.         NamedNodeMap attributes = node.getAttributes();         for (int i=0; i<attributes.getLength(); i++)         {             Node attribute = attributes.item(i);             if (attribute.getNodeName().equalsIgnoreCase(element))             {                 //matches.add(attribute.getNodeValue());                 matches.add(attribute);             }         }         // Search child nodes.         NodeList children = node.getChildNodes();                      for (int i=0; i<children.getLength(); i++)         {             searchAttributes(element,children.item(i));         }     }     /** Searches for a given element in the given node and updates list       * of elements with that name. Recursively searches for sub-nodes of the given one.       * @param element Element to search for.       * @param node Node to start search from.       */     private void searchNode(String element, Node node)     {         // Only consider document or element nodes.         if ((node.getNodeType() != Node.DOCUMENT_NODE) &&             (node.getNodeType() != Node.ELEMENT_NODE))         {             return;         }                    // Match found, so add node to list.           if (node.getNodeName().equalsIgnoreCase(element))         {             matches.add(node);         }                      // Search children         NodeList children = node.getChildNodes();           for (int i=0; i<children.getLength(); i++)         {             searchNode(element,children.item(i));         }     }          /** Converts the given DOM node into XML. Recursively converts       * any child nodes.       * @param node DOM Node to display.       */     private void outputNodeAsXML(Node node)      {         outputNodeAsXML(node,null,null,null);        }          /** Converts the given DOM node into XML. Recursively converts       * any child nodes. This version allows the XML version, encoding and stand-alone       * status to be set.       * @param node DOM Node to display.       * @param version XML version, or null if default ('1.0') is to be used.       * @param encoding XML encoding, or null if encoding is not to be specified.       * @param standalone XML stand-alone status or null if not to be specified.       */     private void outputNodeAsXML(Node node, String version, String encoding, Boolean standalone)      {         // Store node name, type and value.         String name  = node.getNodeName(),                value = makeFriendly(node.getNodeValue());         int    type  = node.getNodeType();         // Ignore empty nodes (e.g. blank lines etc.)         if ((value != null) && (value.trim().equals("")))         {             return;         }              switch (type)          {             case Node.DOCUMENT_NODE:    // Start of document.             {                 if (version == null)                 {                   out.print("<?xml version=\"1.0\" ");                 }                 else                 {                   out.print("<?xml version=\""+version+"\" ");                 }                                  if (encoding != null)                 {                     out.print("encoding=\""+encoding+"\" ");                 }                                  if (standalone != null)                 {                     if (standalone.booleanValue())                     {                         out.print("standalone=\"yes\" ");                         }                     else                     {                         out.print("standalone=\"no\" ");                            }                 }                                  out.println("?>");                 // Output the document's child nodes.                 NodeList children = node.getChildNodes();                                  for (int i=0; i<children.getLength(); i++)                  {                     outputNodeAsXML(children.item(i));                 }                 break;             }             case Node.ELEMENT_NODE:     // Document element with attributes.             {                 // Output opening element tag.                 indent++;                 indent();                           out.print("<"+name);                 // Output any attributes the element might have.                 NamedNodeMap attributes = node.getAttributes();                 for (int i=0; i<attributes.getLength(); i++)                 {                     Node attribute = attributes.item(i);                     out.print(" "+attribute.getNodeName()+"=\""+attribute.getNodeValue()+"\"");                 }                 out.print(">");                 // Output any child nodes that exist.                                     NodeList children = node.getChildNodes();                 for (int i=0; i<children.getLength(); i++)                 {                      outputNodeAsXML(children.item(i));                 }                                break;             }             case Node.CDATA_SECTION_NODE:          // Display text.             case Node.TEXT_NODE:              {                 out.print(value);                 break;             }                          case Node.COMMENT_NODE:                // Comment node.             {                 indent++;                 indent();                  out.print("<!--"+value+"-->");                 indent--;                 break;             }             case Node.ENTITY_REFERENCE_NODE:       // Entity reference nodes.             {                 indent++;                 indent();                  out.print("&"+name+";");                 indent--;                 break;             }             case Node.PROCESSING_INSTRUCTION_NODE: // Processing instruction.             {                 indent++;                 indent();                 out.print("<?"+name);                  if ((value != null) && (value.length() > 0))                  {                     out.print(" "+value);                 }                 out.println("?>");                 indent--;                 break;             }         }                     // Finally output closing tags for each element.         if (type == Node.ELEMENT_NODE)         {             out.print("</"+node.getNodeName()+">");             indent--;             if (node.getNextSibling() == null)             {                 indent();  // Only throw new line if this is the last sibling.             }         }     }     /** Converts a given string into XML-friendly code by replacing        * quotes, triangular brackets etc. with their symbolic equivalent.       * @param text Text to process.       * @return Processed text with XML friendly symbols.       */     private static String makeFriendly(String text)      {         StringBuffer newText = new StringBuffer();         if (text == null)            {             return null;         }         int numCharacters = text.length();         for (int i=0; i<numCharacters; i++)          {             char ch = text.charAt(i);             switch (ch)              {                 case '<':                  {                     newText.append("&lt;");                     break;                 }                 case '>':                  {                     newText.append("&gt;");                     break;                 }                 case '&':                  {                     newText.append("&amp;");                     break;                 }                 case '"':                  {                     newText.append("&quot;");                     break;                 }                 default:                 {                     newText.append(ch);                 }             }         }         return newText.toString();     }      /** Indents output to current tree depth.       */     private void indent()     {         out.println("");         for (int i=1; i<indent; i++)         {             out.print(" ");         }     } }