/*
* Copyright (c) 2002-2003 by OpenSymphony
* All rights reserved.
*/
package com.opensymphony.util;
import com.opensymphony.provider.ProviderFactory;
import com.opensymphony.provider.ProviderInvocationException;
import com.opensymphony.provider.XMLPrinterProvider;
import com.opensymphony.provider.XPathProvider;
import org.w3c.dom.*;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import java.io.*;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
/**
* XMLUtils is a bunch of quick access utility methods to common XML operations.
*
* <p>These include:</p>
*
* <ul>
* <li>Parsing XML stream into org.w3c.dom.Document.
* <li>Creating blank Documents.
* <li>Serializing (pretty-printing) Document back to XML stream.
* <li>Extracting nodes using X-Path expressions.
* <li>Cloning nodes.
* <li>Performing XSL transformations.
* </ul>
*
* <p>This class contains static methods only and is not meant to be instantiated. It also
* contains only basic (common) functions - for more control access appropriate API directly.</p>
*
* @author <a href="mailto:[email protected]">Joe Walnes</a>
* @author <a href="mailto:[email protected]">Hani Suleiman</a>
* @version $Revision: 125 $
*/
public class XMLUtils {
//~ Static fields/initializers /////////////////////////////////////////////
private static final XPathProvider xPathProvider;
private static final XMLPrinterProvider xmlPrinterProvider;
static {
ProviderFactory factory = ProviderFactory.getInstance();
xPathProvider = (XPathProvider) factory.getProvider("xpath.provider", com.opensymphony.provider.xpath.XalanXPathProvider.class.getName());
xmlPrinterProvider = (XMLPrinterProvider) factory.getProvider("xmlprinter.provider",
// com.opensymphony.provider.xmlprinter.XalanXMLPrinterProvider.class.getName()
com.opensymphony.provider.xmlprinter.DefaultXMLPrinterProvider.class.getName());
}
/**
* The cache size for the XSL transforms
*/
private static int cacheSize = 10;
private static HashMap xslCache = new HashMap();
private static LinkedList xslKeyList = new LinkedList();
//~ Methods ////////////////////////////////////////////////////////////////
/**
* Return the contained text within an Element. Returns null if no text found.
*/
public final static String getElementText(Element element) {
NodeList nl = element.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node c = nl.item(i);
if (c instanceof Text) {
return ((Text) c).getData();
}
}
return null;
}
/**
* Clone given Node into target Document. If targe is null, same Document will be used.
* If deep is specified, all children below will also be cloned.
*/
public final static Node cloneNode(Node node, Document target, boolean deep) throws DOMException {
if ((target == null) || (node.getOwnerDocument() == target)) {
// same Document
return node.cloneNode(deep);
} else {
//DOM level 2 provides this in Document, so once xalan switches to that,
//we can take out all the below and just call target.importNode(node, deep);
//For now, we implement based on the javadocs for importNode
Node newNode;
int nodeType = node.getNodeType();
switch (nodeType) {
case Node.ATTRIBUTE_NODE:
newNode = target.createAttribute(node.getNodeName());
break;
case Node.DOCUMENT_FRAGMENT_NODE:
newNode = target.createDocumentFragment();
break;
case Node.ELEMENT_NODE:
Element newElement = target.createElement(node.getNodeName());
NamedNodeMap nodeAttr = node.getAttributes();
if (nodeAttr != null) {
for (int i = 0; i < nodeAttr.getLength(); i++) {
Attr attr = (Attr) nodeAttr.item(i);
if (attr.getSpecified()) {
Attr newAttr = (Attr) cloneNode(attr, target, true);
newElement.setAttributeNode(newAttr);
}
}
}
newNode = newElement;
break;
case Node.ENTITY_REFERENCE_NODE:
newNode = target.createEntityReference(node.getNodeName());
break;
case Node.PROCESSING_INSTRUCTION_NODE:
newNode = target.createProcessingInstruction(node.getNodeName(), node.getNodeValue());
break;
case Node.TEXT_NODE:
newNode = target.createTextNode(node.getNodeValue());
break;
case Node.CDATA_SECTION_NODE:
newNode = target.createCDATASection(node.getNodeValue());
break;
case Node.COMMENT_NODE:
newNode = target.createComment(node.getNodeValue());
break;
case Node.NOTATION_NODE:
case Node.ENTITY_NODE:
case Node.DOCUMENT_TYPE_NODE:
case Node.DOCUMENT_NODE:default:
throw new IllegalArgumentException("Importing of " + node + " not supported yet");
}
if (deep) {
for (Node child = node.getFirstChild(); child != null;
child = child.getNextSibling()) {
newNode.appendChild(cloneNode(child, target, true));
}
}
return newNode;
}
}
/**
* Create blank Document.
*/
public final static Document newDocument() throws ParserConfigurationException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
return builder.newDocument();
}
/**
* Create blank Document, and insert root element with given name.
*/
public final static Document newDocument(String rootElementName) throws ParserConfigurationException {
Document doc = newDocument();
doc.appendChild(doc.createElement(rootElementName));
return doc;
}
/**
* Parse an InputSource of XML into Document.
*/
public final static Document parse(InputSource in) throws ParserConfigurationException, IOException, SAXException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
return builder.parse(in);
}
/**
* Parse an InputStream of XML into Document.
*/
public final static Document parse(InputStream in) throws ParserConfigurationException, IOException, SAXException {
return parse(new InputSource(in));
}
/**
* Parse a Reader of XML into Document.
*/
public final static Document parse(Reader in) throws ParserConfigurationException, IOException, SAXException {
return parse(new InputSource(in));
}
/**
* Parse a File of XML into Document.
*/
public final static Document parse(File file) throws ParserConfigurationException, IOException, SAXException {
return parse(new InputSource(new FileInputStream(file)));
}
/**
* Parse the contents of a URL's XML into Document.
*/
public final static Document parse(URL url) throws ParserConfigurationException, IOException, SAXException {
return parse(new InputSource(url.toString()));
}
/**
* Parse a String containing XML data into a Document.
* Note that String contains XML itself and is not URI.
*/
public final static Document parse(String xml) throws ParserConfigurationException, IOException, SAXException {
return parse(new InputSource(new StringReader(xml)));
}
/**
* Pretty-print a Document to Writer.
*/
public final static void print(Document document, Writer out) throws IOException {
xmlPrinterProvider.print(document, out);
}
/**
* Pretty-print a Document to OutputStream.
*/
public final static void print(Document document, OutputStream out) throws IOException {
print(document, new OutputStreamWriter(out));
}
/**
* Pretty-print a Document to File.
*/
public final static void print(Document document, File file) throws IOException {
print(document, new FileWriter(file));
}
/**
* Pretty-print a Document back to String of XML.
*/
public final static String print(Document document) throws IOException {
StringWriter result = new StringWriter();
print(document, result);
return result.toString();
}
/**
* Perform XSL transformation.
*/
public final static void transform(Reader xml, Reader xsl, Writer result) throws TransformerException {
transform(xml, xsl, result, null);
}
/**
* Return single Node from base Node using X-Path expression.
*/
public final static Node xpath(Node base, String xpath) throws TransformerException {
try {
return xPathProvider.getNode(base, xpath);
} catch (ProviderInvocationException e) {
try {
throw e.getCause();
} catch (TransformerException te) {
throw te;
} catch (Throwable tw) {
tw.printStackTrace();
return null;
}
}
}
/**
* Return multiple Nodes from base Node using X-Path expression.
*/
public final static NodeList xpathList(Node base, String xpath) throws TransformerException {
try {
return xPathProvider.getNodes(base, xpath);
} catch (ProviderInvocationException e) {
try {
throw e.getCause();
} catch (TransformerException te) {
throw te;
} catch (Throwable tw) {
tw.printStackTrace();
return null;
}
}
}
/**
* Sets the internal cache size for XSL sheets
* @param newCacheSize
*/
public static void setCacheSize(int newCacheSize) {
cacheSize = newCacheSize;
}
/**
* Accessor for the internal XSL transformer cache
* @return the cache size
*/
public static int getCacheSize() {
return cacheSize;
}
/**
* This method applies an XSL sheet to an XML document.
* <p>2002/Apr/7, fixed bug 540875, first reported by Erik Weber, and
* added configurable cache size.
* @param xml the XML source
* @param xsl the XSL source
* @param result where to put the response
* @param parameters a map consisting of params for the transformer
* @param xslkey a key used to refer to the XSL
* @throws TransformerException
*/
public final static void transform(Reader xml, Reader xsl, Writer result, Map parameters, String xslkey) throws TransformerException {
try {
Transformer t;
if ((null != xslkey) && (xslCache.containsKey(xslkey))) {
t = (Transformer) xslCache.get(xslkey);
synchronized (xslKeyList) {
xslKeyList.remove(xslkey);
xslKeyList.add(xslkey);
}
} else {
TransformerFactory factory = TransformerFactory.newInstance();
t = factory.newTransformer(new StreamSource(xsl));
if (null != xslkey) {
xslCache.put(xslkey, t);
xslKeyList.add(xslkey);
synchronized (xslKeyList) {
int s = xslKeyList.size();
int cacheSize = getCacheSize();
int iterations = 1;
/* if the cache size was adjusted after the cache is initialized,
* we don't want to shrink TOO fast, just to be nice to runtime
* performance
*
* That's my story, and I'm stickin' to it
*/
if (s > (cacheSize + 1)) {
iterations = 2;
}
while (iterations-- != 0) {
Object removalKey = xslKeyList.get(0);
xslKeyList.remove(0);
xslCache.remove(removalKey);
}
}
}
}
if (parameters != null) {
Iterator i = parameters.keySet().iterator();
while (i.hasNext()) {
Object key = i.next();
Object value = parameters.get(key);
t.setParameter(key.toString(), value.toString());
}
}
t.transform(new StreamSource(xml), new StreamResult(result));
} catch (TransformerConfigurationException tce) {
throw new TransformerException(tce);
}
}
/**
* Perform XSL transformation, with params.
*/
public final static void transform(Reader xml, Reader xsl, Writer result, Map parameters) throws TransformerException {
transform(xml, xsl, result, parameters, xsl.toString());
}
/**
* Perform XSL transformation.
*/
public final static void transform(InputStream xml, InputStream xsl, OutputStream result) throws TransformerException {
transform(new InputStreamReader(xml), new InputStreamReader(xsl), new OutputStreamWriter(result));
}
/**
* Perform XSL transformation. XML and XSL supplied in Strings and result returned as String.
* Note that inputs are actual XML/XSL data, not URIs.
*/
public final static String transform(String xml, String xsl) throws TransformerException {
StringWriter result = new StringWriter();
transform(new StringReader(xml), new StringReader(xsl), result);
return result.toString();
}
/**
* Perform XSL transformations using given Documents and return new Document.
*/
public final static Document transform(Document xml, Document xsl) throws ParserConfigurationException, TransformerException {
try {
Document result = newDocument();
TransformerFactory factory = TransformerFactory.newInstance();
Transformer t = factory.newTransformer(new DOMSource(xsl));
t.transform(new DOMSource(xml), new DOMResult(result));
return result;
} catch (TransformerConfigurationException tce) {
throw new TransformerException(tce);
}
}
}
|