/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program 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.
*
* Copyright 2007 - 2008 Pentaho Corporation. All rights reserved.
*/
package org.pentaho.platform.util.xml.dom4j;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringWriter;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.URIResolver;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Node;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.pentaho.platform.api.util.XmlParseException;
import org.pentaho.platform.util.messages.Messages;
import org.xml.sax.EntityResolver;
// TODO sbarkdull, exernalize strings, comment methods
/**
* A set of static methods to help in with: * the construction of XML DOM
* Documents (org.dom4j.Document) from files, streams, and Strings * in the
* creation of XML DOM Documents as the result of an XSLT transform * persisting
* of XML DOM documents to the file system or a <code>Writer</code>. * the
* encoding of a String of Xml text
*
* Design notes: This class should never have any dependencies (i.e. imports) on
* anything on org.pentaho or com.pentaho or their decendant packages. In
* general, methods in the class should not attempt to handle exceptions, but
* should let the exceptions propogate to the caller to be handled there. Please
* do not use european-reuse in this class. One of the primary design goals for
* this class was to construct it in a way that it could be used without change
* outside of the Pentaho platform. Related XML-helper type code that is
* dependant on the platform should be moved "up" to XmlHelper.
*/
public class XmlDom4JHelper {
private static final Log logger = LogFactory.getLog(XmlDom4JHelper.class);
/**
* Create a <code>Document</code> from <code>str</code>.
*
* @param str
* String containing the XML that will be used to create the Document
* @param resolver EntityResolver an instance of an EntityResolver that will resolve
* any external URIs. See the docs on EntityResolver. null is an acceptable value.
* @return <code>Document</code> initialized with the xml in
* <code>strXml</code>.
* @throws XmlParseException
*/
public static Document getDocFromString(final String strXml, final EntityResolver resolver) throws XmlParseException{
Document document = null;
try {
document = XmlDom4JHelper.getDocFromStream(new ByteArrayInputStream(strXml.getBytes()), resolver);
} catch (DocumentException e) {
throw new XmlParseException(Messages.getInstance().getErrorString("XmlDom4JHelper.ERROR_0001_UNABLE_TO_GET_DOCUMENT_FROM_STRING"), e); //$NON-NLS-1$
} catch (IOException e) {
throw new XmlParseException(Messages.getInstance().getErrorString("XmlDom4JHelper.ERROR_0002_UNSUPPORTED_ENCODING"), e); //$NON-NLS-1$
}
return document;
}
/**
* Create a <code>Document</code> from the contents of a file.
*
* @param path
* String containing the path to the file containing XML that will be
* used to create the Document.
* @param resolver EntityResolver an instance of an EntityResolver that will resolve
* any external URIs. See the docs on EntityResolver. null is an acceptable value.
* @return <code>Document</code> initialized with the xml in
* <code>strXml</code>.
* @throws DocumentException
* if the document isn't valid
* @throws IOException
* if the file doesn't exist
*/
public static Document getDocFromFile(final File file, final EntityResolver resolver) throws DocumentException, IOException {
SAXReader reader = new SAXReader();
if (resolver != null) {
reader.setEntityResolver(resolver);
}
return reader.read(file);
}
/**
* Create a <code>Document</code> from the contents of an input stream,
* where the input stream contains valid XML.
*
* @param inStream
* @return
* @throws DocumentException
* @throws IOException
*/
public static Document getDocFromStream(final InputStream inStream, final EntityResolver resolver)
throws DocumentException, IOException {
SAXReader reader = new SAXReader();
if (resolver != null) {
reader.setEntityResolver(resolver);
}
return reader.read(inStream);
}
/**
* Create a <code>Document</code> from the contents of an input stream,
* where the input stream contains valid XML.
*
* @param inStream
* @return
* @throws DocumentException
* @throws IOException
*/
public static Document getDocFromStream(final InputStream inStream) throws DocumentException, IOException {
return XmlDom4JHelper.getDocFromStream(inStream, null);
}
/**
* Use the transform specified by xslSrc and transform the document specified
* by docSrc, and return the resulting document.
*
* @param xslSrc
* StreamSrc containing the xsl transform
* @param docSrc
* StreamSrc containing the document to be transformed
* @param params
* Map of properties to set on the transform
* @param resolver
* URIResolver instance to resolve URI's in the output document.
*
* @return StringBuffer containing the XML results of the transform
* @throws TransformerConfigurationException
* if the TransformerFactory fails to create a Transformer.
* @throws TransformerException
* if actual transform fails.
*/
protected static final StringBuffer transformXml(final StreamSource xslSrc, final StreamSource docSrc,
final Map params, final URIResolver resolver) throws TransformerConfigurationException, TransformerException {
StringBuffer sb = null;
StringWriter writer = new StringWriter();
TransformerFactory tf = TransformerFactory.newInstance();
if (null != resolver) {
tf.setURIResolver(resolver);
}
// TODO need to look into compiling the XSLs...
Transformer t = tf.newTransformer(xslSrc); // can throw
// TransformerConfigurationException
// Start the transformation
if (params != null) {
Set<?> keys = params.keySet();
Iterator<?> it = keys.iterator();
String key, val;
while (it.hasNext()) {
key = (String) it.next();
val = (String) params.get(key);
if (val != null) {
t.setParameter(key, val);
}
}
}
t.transform(docSrc, new StreamResult(writer)); // can throw
// TransformerException
sb = writer.getBuffer();
return sb;
}
/**
* Convert a W3C Document to a String.
*
* Note: if you are working with a dom4j Document, you can use it's asXml()
* method.
*
* @param doc
* org.w3c.dom.Document to be converted to a String.
* @return String representing the XML document.
*
* @throws TransformerConfigurationException
* If unable to get an instance of a Transformer
* @throws TransformerException
* If the attempt to transform the document fails.
*/
public static final StringBuffer docToString(final org.w3c.dom.Document doc)
throws TransformerConfigurationException, TransformerException {
StringBuffer sb = null;
StringWriter writer = new StringWriter();
TransformerFactory tf = TransformerFactory.newInstance();
Transformer t = tf.newTransformer(); // can throw
// TransformerConfigurationException
Source docSrc = new DOMSource(doc);
t.transform(docSrc, new StreamResult(writer)); // can throw
// TransformerException
sb = writer.getBuffer();
return sb;
}
// TODO sbarkdull, this code is duplicated in LocaleHelper
/**
* convert any character in the XML input (<code>rawValue</code>) whose
* code position is greater than or equal to 0x080 to its Numeric Character
* Reference. For a description of Numeric Character References see:
* http://www.w3.org/TR/html4/charset.html#h-5.3.1
*
* @param rawValue
* String containing the XML to be encoded.
* @return String containing the encoded XML
*/
public static String getXmlEncodedString(final String rawValue) {
StringBuffer value = new StringBuffer();
for (int n = 0; n < rawValue.length(); n++) {
int charValue = rawValue.charAt(n);
if (charValue >= 0x80) {
value.append(""); //$NON-NLS-1$
value.append(Integer.toString(charValue, 0x10));
value.append(";"); //$NON-NLS-1$
} else {
value.append((char) charValue);
}
}
return value.toString();
}
/**
* Write an XML document to a file using the specified character encoding.
*
* @param doc
* Document to be written
* @param outputStream
* the output stream
* @param encoding
* String specifying the character encoding. Can be null, in which
* case the default encoding will be used. See
* http://java.sun.com/j2se/1.5.0/docs/api/java/io/OutputStreamWriter.html
* @throws IOException
*/
public static void saveDom(final Document doc, final OutputStream outputStream, String encoding) throws IOException {
saveDom(doc, outputStream, encoding, false);
}
public static void saveDom(final Document doc, final OutputStream outputStream, String encoding, boolean suppressDeclaration) throws IOException {
saveDom(doc, outputStream, encoding, suppressDeclaration, false);
}
public static void saveDom(final Document doc, final OutputStream outputStream, String encoding, boolean suppressDeclaration, boolean prettyPrint) throws IOException {
OutputFormat format = prettyPrint ? OutputFormat.createPrettyPrint() : OutputFormat.createCompactFormat();
format.setSuppressDeclaration(suppressDeclaration);
if (encoding != null) {
format.setEncoding(encoding.toLowerCase());
}
if (!suppressDeclaration) {
doc.setXMLEncoding(encoding.toUpperCase());
}
XMLWriter writer = new XMLWriter(outputStream, format);
writer.write(doc);
writer.flush();
}
/**
* Convenience method to close an input stream and handle (log and throw away)
* any exceptions. Helps keep code uncluttered.
*
* @param strm
* InputStream to be closed
*/
protected static void closeInputStream(final InputStream strm) {
if (null != strm) {
try {
strm.close();
} catch (IOException e) {
XmlDom4JHelper.logger.warn("Failed to close InputStream.", e); //$NON-NLS-1$
}
}
}
public static String getNodeText(final String xpath, final Node rootNode) {
return (XmlDom4JHelper.getNodeText(xpath, rootNode, null));
}
public static long getNodeText(final String xpath, final Node rootNode, final long defaultValue) {
String valueStr = XmlDom4JHelper.getNodeText(xpath, rootNode, Long.toString(defaultValue));
try {
return Long.parseLong(valueStr);
} catch (Exception ignored) {
}
return defaultValue;
}
public static double getNodeText(final String xpath, final Node rootNode, final double defaultValue) {
String valueStr = XmlDom4JHelper.getNodeText(xpath, rootNode, null);
if (valueStr == null) {
return defaultValue;
}
try {
return Double.parseDouble(valueStr);
} catch (Exception ignored) {
}
return defaultValue;
}
public static String getNodeText(final String xpath, final Node rootNode, final String defaultValue) {
if (rootNode == null) {
return (defaultValue);
}
Node node = rootNode.selectSingleNode(xpath);
if (node == null) {
return defaultValue;
}
return node.getText();
}
}