Mercurial > dive4elements > framework
diff artifacts-common/src/main/java/org/dive4elements/artifacts/common/utils/XMLUtils.java @ 472:783cc1b6b615
Moved directories to org.dive4elements
author | Sascha L. Teichmann <teichmann@intevation.de> |
---|---|
date | Thu, 25 Apr 2013 10:53:15 +0200 |
parents | artifacts-common/src/main/java/de/intevation/artifacts/common/utils/XMLUtils.java@cdc8b7c64856 |
children | 415df0fc4fa1 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/artifacts-common/src/main/java/org/dive4elements/artifacts/common/utils/XMLUtils.java Thu Apr 25 10:53:15 2013 +0200 @@ -0,0 +1,617 @@ +/* + * Copyright (c) 2010 by Intevation GmbH + * + * This program is free software under the LGPL (>=v2.1) + * Read the file LGPL.txt coming with the software for details + * or visit http://www.gnu.org/licenses/ if it does not exist. + */ + +package de.intevation.artifacts.common.utils; + +import java.util.List; +import java.util.ArrayList; +import java.util.Map; +import java.util.LinkedHashMap; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +import java.io.ByteArrayInputStream; +import java.io.FileInputStream; +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.StringWriter; + +import javax.xml.namespace.NamespaceContext; +import javax.xml.namespace.QName; + +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.TransformerFactoryConfigurationError; + +import javax.xml.transform.dom.DOMSource; + +import javax.xml.transform.stream.StreamResult; + +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; +import javax.xml.xpath.XPathVariableResolver; + +import org.apache.log4j.Logger; + +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import org.xml.sax.SAXException; + +/** + * Some helper functions to ease work with XML concering namespaces, XPATH + * and so on. + * @author <a href="mailto:sascha.teichmann@intevation.de">Sascha L. Teichmann</a> + */ +public final class XMLUtils +{ + /** W3C URL of XForms. */ + public static final String XFORM_URL = "http://www.w3.org/2002/xforms"; + + /** W3C prefix of XForms. */ + public static final String XFORM_PREFIX = "xform"; + + /** Logger for this class. */ + private static Logger logger = Logger.getLogger(XMLUtils.class); + + private XMLUtils() { + } + + /** + * Helper class to generate elements and attributes with + * namespaces. + */ + public static class ElementCreator + { + /** Owner document of the elements to be created. */ + protected Document document; + + /** Namespace to be used. */ + protected String ns; + + /** Prefix to be used. */ + protected String prefix; + + /** + * Constructor to create an element/attribute creator + * with a given namespace and namespace prefix using a + * given owner document. + * @param document The owning document + * @param ns The namespace + * @param prefix The namespace prefix + */ + public ElementCreator(Document document, String ns, String prefix) { + this.document = document; + this.ns = ns; + this.prefix = prefix; + } + + /** + * Creates a new element using the owning document with + * the this creators namespace and namespace prefix. + * @param name The name of the element + * @return The new element + */ + public Element create(String name) { + Element element = document.createElementNS(ns, name); + element.setPrefix(prefix); + return element; + } + + /** + * Adds a new attribute and its value to a given element. + * It does not set the namespace prefix. + * @param element The element to add the attribute to + * @param name The name of the attribute + * @param value The value of the attribute + */ + public void addAttr(Element element, String name, String value) { + addAttr(element, name, value, false); + } + + /** + * Adds a new attribute and its value to a given element. + * If the namespace prefix is used is decided by the 'addPrefix' flag. + * @param element The element to add the attribute to + * @param name The name of the attribute + * @param value The value of the attribute + * @param addPrefix If true the creators namespace prefix is + * set on the attribute. + */ + public void addAttr( + Element element, + String name, + String value, + boolean addPrefix + ) { + if (addPrefix) { + Attr attr = document.createAttributeNS(ns, name); + attr.setValue(value); + attr.setPrefix(prefix); + + element.setAttributeNode(attr); + } + else { + element.setAttribute(name, value); + } + } + } // class ElementCreator + + /** + * Creates a new XML document + * @return the new XML document ot null if something went wrong during + * creation. + */ + public static final Document newDocument() { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + + try { + return factory.newDocumentBuilder().newDocument(); + } + catch (ParserConfigurationException pce) { + logger.error(pce.getLocalizedMessage(), pce); + } + return null; + } + + + /** + * Create xml/string representation of element (nested in otherwise empty + * document). + * @param element element to inspect in string. + * @return string with xml representation of element. + */ + public final static String toString(Node node) { + Document doc = newDocument(); + doc.appendChild(doc.importNode(node,true)); + return toString(doc); + } + + + /** + * Loads a XML document namespace aware from a file + * @param file The file to load. + * @return the XML document or null if something went wrong + * during loading. + */ + public static final Document parseDocument(File file) { + InputStream inputStream = null; + try { + inputStream = new BufferedInputStream(new FileInputStream(file)); + return parseDocument(inputStream); + } + catch (IOException ioe) { + logger.error(ioe.getLocalizedMessage(), ioe); + } + finally { + if (inputStream != null) { + try { inputStream.close(); } + catch (IOException ioe) {} + } + } + return null; + } + + /** + * Parses a String to a xml document. + * + * @param string The xml string + * @return the XML document or null if something went wrong. + */ + public static final Document parseDocument(String string) { + InputStream inputStream = new ByteArrayInputStream(string.getBytes()); + return parseDocument(inputStream); + } + + + public static final Document parseDocument(InputStream inputStream) { + return parseDocument(inputStream, Boolean.TRUE); + } + + public static final Document parseDocument( + InputStream inputStream, + Boolean namespaceAware + ) { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + + if (namespaceAware != null) { + factory.setNamespaceAware(namespaceAware.booleanValue()); + } + + try { + return factory.newDocumentBuilder().parse(inputStream); + } + catch (ParserConfigurationException pce) { + logger.error(pce.getLocalizedMessage(), pce); + } + catch (SAXException se) { + logger.error(se.getLocalizedMessage(), se); + } + catch (IOException ioe) { + logger.error(ioe.getLocalizedMessage(), ioe); + } + return null; + } + + /** + * Creates a new XPath without a namespace context. + * @return the new XPath. + */ + public static final XPath newXPath() { + return newXPath(null, null); + } + + /** + * Creates a new XPath with a given namespace context. + * @param namespaceContext The namespace context to be used or null + * if none should be used. + * @return The new XPath + */ + public static final XPath newXPath( + NamespaceContext namespaceContext, + XPathVariableResolver resolver) + { + XPathFactory factory = XPathFactory.newInstance(); + XPath xpath = factory.newXPath(); + if (namespaceContext != null) { + xpath.setNamespaceContext(namespaceContext); + } + + if (resolver != null) { + xpath.setXPathVariableResolver(resolver); + } + return xpath; + } + + /** + * Evaluates an XPath query on a given object and returns the result + * as a given type. No namespace context is used. + * @param root The object which is used as the root of the tree to + * be searched in. + * @param query The XPath query + * @param returnTyp The type of the result. + * @return The result of type 'returnTyp' or null if something + * went wrong during XPath evaluation. + */ + public static final Object xpath( + Object root, + String query, + QName returnTyp + ) { + return xpath(root, query, returnTyp, null); + } + + /** + * Evaluates an XPath query on a given object and returns the result + * as a string. A given namespace context is used. + * @param root The object which is used as the root of the tree to + * be searched in. + * @param query The XPath query + * @param namespaceContext The namespace context to be used or null + * if none should be used. + * @return The result of the query or null if something went wrong + * during XPath evaluation. + */ + public static final String xpathString( + Object root, String query, NamespaceContext namespaceContext + ) { + return (String)xpath( + root, query, XPathConstants.STRING, namespaceContext); + } + + /** + * Evaluates an XPath query on a given object and returns the result + * as a given type. Optionally a namespace context is used. + * @param root The object which is used as the root of the tree to + * be searched in. + * @param query The XPath query + * @param returnType The type of the result. + * @param namespaceContext The namespace context to be used or null + * if none should be used. + * @return The result of type 'returnTyp' or null if something + * went wrong during XPath evaluation. + */ + public static final Object xpath( + Object root, + String query, + QName returnType, + NamespaceContext namespaceContext + ) { + return xpath(root, query, returnType, namespaceContext, null); + } + + public static final Object xpath( + Object root, + String query, + QName returnType, + NamespaceContext namespaceContext, + Map<String, String> variables) + { + if (root == null) { + return null; + } + + XPathVariableResolver resolver = variables != null + ? new MapXPathVariableResolver(variables) + : null; + + try { + XPath xpath = newXPath(namespaceContext, resolver); + if (xpath != null) { + return xpath.evaluate(query, root, returnType); + } + } + catch (XPathExpressionException xpee) { + logger.error(xpee.getLocalizedMessage(), xpee); + } + + return null; + } + + /** + * Streams out an XML document to a given output stream. + * @param document The document to be streamed out. + * @param out The output stream to be used. + * @return true if operation succeeded else false. + */ + public static boolean toStream(Document document, OutputStream out) { + try { + Transformer transformer = + TransformerFactory.newInstance().newTransformer(); + DOMSource source = new DOMSource(document); + StreamResult result = new StreamResult(out); + transformer.transform(source, result); + return true; + } + catch (TransformerConfigurationException tce) { + logger.error(tce.getLocalizedMessage(), tce); + } + catch (TransformerFactoryConfigurationError tfce) { + logger.error(tfce.getLocalizedMessage(), tfce); + } + catch (TransformerException te) { + logger.error(te.getLocalizedMessage(), te); + } + + return false; + } + + public static String toString(Document document) { + try { + Transformer transformer = + TransformerFactory.newInstance().newTransformer(); + DOMSource source = new DOMSource(document); + StringWriter out = new StringWriter(); + StreamResult result = new StreamResult(out); + transformer.transform(source, result); + out.flush(); + return out.toString(); + } + catch (TransformerConfigurationException tce) { + logger.error(tce.getLocalizedMessage(), tce); + } + catch (TransformerFactoryConfigurationError tfce) { + logger.error(tfce.getLocalizedMessage(), tfce); + } + catch (TransformerException te) { + logger.error(te.getLocalizedMessage(), te); + } + + return null; + } + + public static byte [] toByteArray(Document document) { + return toByteArray(document, false); + } + + /** + * Transforms an XML document into a byte array. + * @param document The document to be streamed out. + * @param compress The document should be compressed, too. + * @return the byte array or null if operation failed or + * document is null. + */ + public static byte [] toByteArray(Document document, boolean compress) { + if (document != null) { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + OutputStream out = compress + ? new GZIPOutputStream(baos) + : baos; + boolean success = toStream(document, out); + out.flush(); + out.close(); + return success + ? baos.toByteArray() + : null; + } + catch (IOException ioe) { + logger.error(ioe); + } + } + return null; + } + + public static Document fromByteArray(byte [] data) { + return fromByteArray(data, false); + } + + public static Document fromByteArray(byte [] data, boolean decompress) { + if (data != null) { + InputStream in = new ByteArrayInputStream(data); + try { + if (decompress) { + in = new GZIPInputStream(in); + } + return parseDocument(in); + } + catch (IOException ioe) { + logger.error(ioe); + } + finally { + try { + in.close(); + } + catch (IOException ioe) { + logger.error(ioe); + } + } + } + return null; + } + + private static class BuildResult { + List<Node> children; + Map<String, String> attributes; + BuildResult() { + children = new ArrayList<Node>(); + attributes = new LinkedHashMap<String, String>(); + } + + void setAttributes(Element element) { + for (Map.Entry<String, String> entry: attributes.entrySet()) { + element.setAttribute(entry.getKey(), entry.getValue()); + } + } + + void finish(Element element) { + setAttributes(element); + for (Node child: children) { + element.appendChild(child); + } + } + + void add(Node node) { + children.add(node); + } + + void add(String key, Object value) { + attributes.put(key, value != null ? value.toString() : "null"); + } + + int numChildren() { + return children.size(); + } + + Node firstChild() { + return children.get(0); + } + } // class BuildResult + + private static BuildResult recursiveBuild( + List list, + Document document + ) { + BuildResult result = new BuildResult(); + for (Object entry: list) { + if (entry instanceof Map) { + BuildResult subResult = recursiveBuild( + (Map<String, Object>)entry, document); + if (subResult.numChildren() == 1) { + result.add(subResult.firstChild()); + } + else { + Element element = document.createElement("map"); + subResult.finish(element); + result.add(element); + } + } + else if (entry instanceof List) { + Element element = document.createElement("list"); + BuildResult subResult = recursiveBuild((List)entry, document); + subResult.finish(element); + result.add(element); + } + else { + Element element = document.createElement("entry"); + element.setAttribute( + "value", + entry != null ? entry.toString() : "null"); + } + } + return result; + } + + private static BuildResult recursiveBuild( + Map<String, Object> map, + Document document + ) { + BuildResult result = new BuildResult(); + + List<Node> nodes = new ArrayList<Node>(); + for (Map.Entry<String, Object> entry: map.entrySet()) { + Object value = entry.getValue(); + if (value instanceof Map) { + Element element = document.createElement(entry.getKey()); + BuildResult subResult = recursiveBuild( + (Map<String, Object>)value, document); + subResult.finish(element); + result.add(element); + } + else if (value instanceof List) { + Element element = document.createElement(entry.getKey()); + BuildResult subResult = recursiveBuild((List)value, document); + subResult.finish(element); + result.add(element); + } + else { + result.add(entry.getKey(), value); + } + } + return result; + } + + public static Document jsonToXML(String input) { + Document document = newDocument(); + + if (document == null) { + return null; + } + + Map<String, Object> map; + try { + map = JSON.parse(input); + } + catch (IOException ioe) { + logger.error(ioe); + return null; + } + + BuildResult roots = recursiveBuild(map, document); + + int N = roots.children.size(); + + if (N == 1) { + document.appendChild(roots.children.get(0)); + } + else if (N > 1) { + Node root = document.createElement("root"); + for (int i = 0; i < N; ++i) { + root.appendChild(roots.children.get(i)); + } + document.appendChild(root); + } + + return document; + } +} +// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :