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