package com.mobius.utils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.dom4j.Branch;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.dom4j.xpath.DefaultXPath;
import org.xml.sax.SAXException;
/**
* A utility class to parse XML Documents.
* Requires Dom4J and Jaxen.
*
* <p>
* Change Log:
* </p>
* <ul>
* <li>v1.0 - Initial version of this class - Created by Kevin Matassa</li>
* <li>v1.1 - Added xPath Query support (Marcel Boucher)</li>
* <li>v2.0 - Added dom4j support to enable default namespace support (Marcel Boucher)</li>
* <li>v2.1 - Added support for XSLT transformations (Marcel Boucher)</li>
* <li>v2.2 - Added ability to create new elements and attributes. (Marcel Boucher)</li>
* <li>v2.3 - Added ability to load an XML file from a URL (Mike Hodgson)</li>
* <li>v2.4 - Modified load (URL) to use FileUtils URL method (to fix block errors) and made class final (Mike Hodgson) </li>
* <li>v2.5 - Modified the setValue() method to convert null to empty string. XML DOM will not allow nulls. (Marcel Boucher)</li>
* <li>v2.6 - Added the ability to validate the XML document against XML Schema</li>
* </ul>
*
* @author Marcel Boucher
* @author mboucher@adobe.com
* @version 2.6
*
*/
public final class XMLUtils {
private SAXReader reader;
private Document document;
private Node m_oRootElement;
private byte[] m_XSLT = null;
private org.jaxen.SimpleNamespaceContext namespaceContext = new org.jaxen.SimpleNamespaceContext();
boolean validating = false;
/**
* Load the XML document as byte[] into the XML Parser.
* @param XMLBytes XML Document in bytes to be Parsed
* @return boolean
* @throws IOException
* @throws DocumentException
*/
public boolean load (byte[] XMLBytes) throws IOException, DocumentException, SAXException
{
reader = new SAXReader();
reader.setValidation(validating);
document = reader.read(new ByteArrayInputStream(XMLBytes));
return init();
}
/**
* Gets the root node of the XML object
* @return org.dom4j.Node
*/
public Node getRootNode(){
return m_oRootElement;
}
/**
* Load the XML Document as String into the XML Parser.
* @param path Path to the XML File to load.
* @return boolean
* @throws Exception
* @throws DocumentException
* @throws IOException
*/
public boolean load (String path) throws IOException, DocumentException, Exception
{
FileInputStream oXMLFile = new FileInputStream (path);
byte bArray[] = new byte[oXMLFile.available()];
// Read in the path.
oXMLFile.read (bArray);
oXMLFile.close();
// Load it.
return load (bArray);
}
/**
* Read an XML file in from a URL location
* @param source - a java.net.URL object representing the XML file source
* @return boolean
* @throws MalformedURLException
* @throws IOException
* @throws DocumentException
*/
// public boolean load (URL source)throws MalformedURLException, IOException, DocumentException, Exception {
// byte bArray[] = FileUtils.readURL(source);
// return this.load(bArray);
// }
/**
* Init function that will create an instance of the XML DOM.
* @return boolean
*/
private boolean init()
{
m_oRootElement = document.getDocument().getRootElement();
return document.hasContent();
}
/**
* private function that returns a boolean if the document is loaded.
* @return boolean
*/
private boolean loaded()
{
return document.hasContent();
}
/**
* Adds NameSpace declarations to the xPath context.
* @param prefix Prefix to be used for the namespace
* @param uri Unique resource identifier for the namespace
*/
public void addNameSpace(String prefix, String uri)
{
namespaceContext.addNamespace(prefix,uri);
}
/**
* Perform an xPath Query and return all nodes that match.
* @param xPathStmt
* @return List
* @throws DocumentException
* @throws Exception
*/
@SuppressWarnings("rawtypes")
public List selectNodes(String xPathStmt)throws DocumentException, Exception {
if (!loaded()){
throw new Exception ("XML Document not loaded.");
}
DefaultXPath dxp = new DefaultXPath(xPathStmt);
dxp.setNamespaceContext(namespaceContext);
List nodes = dxp.selectNodes(m_oRootElement);
return nodes;
}
/**
* Perform an xPath Query and return the first node that matches.
* @param xPathStmt
* @return Node
* @throws DocumentException
* @throws Exception
*/
public Node selectSingleNode(String xPathStmt) throws DocumentException, Exception {
if(!loaded()){
throw new Exception("XML Document not loaded.");
}
DefaultXPath dxp = new DefaultXPath(xPathStmt);
dxp.setNamespaceContext(namespaceContext);
Node node = dxp.selectSingleNode(m_oRootElement);
return node;
}
/**
* Perform an xPath Query and return the text value of the node that matched.
* @param xPathStmt xPath statement to execute on the XML Document
* @return String
* @throws SAXException
* @throws Exception
*/
public String getValue(String xPathStmt) throws DocumentException, Exception {
if(!loaded()){
throw new Exception("XML Document not Loaded.");
}
Node oNode = selectSingleNode(xPathStmt);
return oNode.getStringValue();
}
/**
* Perform an xPath Query and set the text value of the node that matched.
* @param xPathStmt xPath statement to execute on the XML Document
* @param newValue The value to be assigned to the XML element returned by the xPath Statement.
* @return void()
* @throws DocumentException
* @throws Exception
*/
public void setValue(String xPathStmt, String newValue) throws DocumentException, Exception
{
if(!loaded())
{
throw new Exception("XML Document not Loaded.");
}
Node oNode = selectSingleNode(xPathStmt);
if((newValue == null) || (newValue == "null"))
newValue="";
oNode.setText(newValue);
}
/**
* Sets the value of the passed in node.
* @param xmlNode object containing a reference to the Node where the value should be set.
* @param newValue The value to be assigned to the XML element returned by the xPath Statement.
* @return void()
* @throws DocumentException
* @throws Exception
*/
public void setValue(Node xmlNode,String newValue) throws DocumentException, Exception
{
if(!loaded())
{
throw new Exception("XML Document not Loaded.");
}
if((newValue == null) || (newValue == "null"))
newValue="";
xmlNode.setText(newValue);
}
/**
* Returns the XML String representation of the XML document.
* @return String
* @throws Exception
*/
public String xml() throws Exception
{
if (!loaded())
throw new Exception ("XML File not loaded");
// Output result
OutputFormat format = OutputFormat.createCompactFormat();
format.setIndentSize(4);
ByteArrayOutputStream os = new ByteArrayOutputStream();
XMLWriter xmlWriter = new XMLWriter(os, format);
xmlWriter.write(document.getDocument());
return os.toString("UTF8");
}
/**
* Returns the XML document as a byte[].
* @return byte[]
* @throws Exception
*/
public byte[] xmlAsBytes() throws Exception
{
if (!loaded())
throw new Exception ("XML File not loaded");
// Output result
OutputFormat format = OutputFormat.createCompactFormat();
format.setIndentSize(4);
ByteArrayOutputStream os = new ByteArrayOutputStream();
XMLWriter xmlWriter = new XMLWriter(os, format);
xmlWriter.write(document.getDocument());
return os.toByteArray();
}
/**
* Load an external XSLT from the file system.
* @param xsltPath - Absolute path to the XSLT file.
* @return void()
* @throws IOException
*/
// public void loadXSLT(String xsltPath) throws IOException
// {
// boolean exists = FileUtils.fileExists(xsltPath);
// if(exists)
// {
// FileInputStream oXSLFile = new FileInputStream (xsltPath);
// byte bArray[] = new byte[oXSLFile.available()];
//
// // Read in the path.
// oXSLFile.read (bArray);
// m_XSLT = bArray;
// oXSLFile.close();
// }
//
// }
/**
* Load an XSLT document as a byte array.
* @param xsltBytes - Byte array representation of the XSLT document.
* @return void()
*/
public void loadXSLT(byte[] xsltBytes)
{
m_XSLT = xsltBytes;
}
/**
* Apply the loaded XSLT to the XML document loaded in memore.
* The result of the transformation will be loaded as the base XML document.
* @return void()
* @throws Exception
*/
public void applyXSLT() throws Exception
{
if(m_XSLT != null && this.xmlAsBytes().length > 0)
{
TransformerFactory tFactory = TransformerFactory.newInstance();
ByteArrayOutputStream out = new ByteArrayOutputStream();
Result r = new StreamResult(out);
ByteArrayInputStream bXSLT = new ByteArrayInputStream(m_XSLT);
ByteArrayInputStream bInput = new ByteArrayInputStream(xmlAsBytes());
Transformer transformer = tFactory.newTransformer (new javax.xml.transform.stream.StreamSource(bXSLT));
transformer.transform
(new javax.xml.transform.stream.StreamSource(bInput),r);
this.load(out.toByteArray());
}
else
{
if(m_XSLT == null)
{
throw new Exception("XSLT not loaded.");
}
if(!loaded())
{
throw new Exception("XML File not loaded.");
}
}
}
/**
* Create a new Node to the currently loaded document.
* @param parentElement - The parent element that will have the new element.
* @param elementName - The name of the new element to be added.
* @return node object continaing a reference to the newly created element.
*/
public Node createNode(Node parentElement,String elementName) throws Exception
{
if (!loaded())
throw new Exception ("XML File not loaded");
Element _parent = (Element) parentElement;
Element _child = _parent.addElement(elementName);
return _child;
}
/**
* Create a new attribute in the currently loaded document.
* @param parentElement - The parent element that will contain this attribute.
* @param attributeName - The name of the attribute to be created.
* @param attributeValue - The value of the newly created attribute.
* @return Node object containing a reference to the new Attribute.
*/
public Node createAttribute(Node parentElement, String attributeName, String attributeValue) throws Exception
{
if (!loaded())
throw new Exception ("XML File not loaded");
Element _parent = (Element) parentElement;
Element _child = _parent.addAttribute(attributeName,attributeValue);
return _child;
}
/**
*
* @param rootNodeName
* @throws Exception
*/
public void createNewXMLDocument(String rootNodeName) throws Exception
{
document = DocumentHelper.createDocument();
document.addElement(rootNodeName);
init();
}
/**
* Inserts a child node under the specified parent node
* @param parentNode - the parent node's xpath statement
* @param insertNode - an org.dom4j.Node object to insert under the parent
*/
public void insertNode(Node parentNode, Node insertNode)throws Exception{
//we can't append Nodes, but a Branch is a Subinterface of Node
Branch parentBranch = (Branch) parentNode;
parentBranch.appendContent((Branch) insertNode);
}
/**
* Returns DOM document as formatted xml String
* @param doc - the xml document
*/
public static String xmlToString(org.w3c.dom.Document doc)
throws Exception
{
StringWriter out = new StringWriter();
Transformer xform = TransformerFactory.newInstance().newTransformer();
xform.setOutputProperty(OutputKeys.INDENT, "yes");
xform.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
xform.transform(new DOMSource(doc), new StreamResult(out));
return out.toString();
}
}