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:
teichmann@475: package org.dive4elements.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;
tom@546: import java.io.FileReader;
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;
tom@546: import javax.xml.parsers.DocumentBuilder;
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;
tom@546: import org.xml.sax.EntityResolver;
tom@546: import org.xml.sax.InputSource;
tom@546:
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: /**
tom@546: * Resolver for entities in artifacts configuration.
tom@546: */
tom@546: private static final EntityResolver CONF_RESOLVER = new EntityResolver() {
tom@546: @Override
tom@546: public InputSource resolveEntity(
tom@546: String publicId,
tom@546: String systemId
tom@546: ) throws SAXException, IOException {
tom@546: return new InputSource(
tom@546: new FileReader(Config.replaceConfigDir(systemId)));
tom@546: }
tom@546: };
tom@546:
tom@546:
tom@546: /**
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: ) {
tom@546: return parseDocument(
tom@546: inputStream, namespaceAware, CONF_RESOLVER);
tom@546: }
tom@546:
tom@546: public static final Document parseDocument(
tom@546: InputStream inputStream,
tom@546: Boolean namespaceAware,
tom@546: EntityResolver entityResolver
tom@546: ) {
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 {
tom@546: DocumentBuilder builder = factory.newDocumentBuilder();
tom@546: builder.setEntityResolver(entityResolver);
tom@546: return builder.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@392:
sascha@392: int numChildren() {
sascha@392: return children.size();
sascha@392: }
sascha@392:
sascha@392: Node firstChild() {
sascha@392: return children.get(0);
sascha@392: }
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: BuildResult subResult = recursiveBuild(
sascha@381: (Map)entry, document);
sascha@392: if (subResult.numChildren() == 1) {
sascha@392: result.add(subResult.firstChild());
sascha@392: }
sascha@392: else {
sascha@392: Element element = document.createElement("map");
sascha@392: subResult.finish(element);
sascha@392: result.add(element);
sascha@392: }
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 :