/** * GRANITE DATA SERVICES * Copyright (C) 2006-2015 GRANITE DATA SERVICES S.A.S. * * This file is part of the Granite Data Services Platform. * * Granite Data Services is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * Granite Data Services 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. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA, or see <http://www.gnu.org/licenses/>. */ package org.granite.util; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.StringReader; import java.io.StringWriter; import java.lang.reflect.Method; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Templates; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.EntityResolver; import org.xml.sax.ErrorHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; /** * @author Franck WOLFF */ public class StdXMLUtil implements XMLUtil { protected static final String TO_STRING_XSL = "<?xml version='1.0' encoding='UTF-8'?>" + "<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>" + " <xsl:strip-space elements='*'/>" + " <xsl:template match='/'>" + " <xsl:copy-of select='*'/>" + " </xsl:template>" + "</xsl:stylesheet>"; private DocumentBuilderFactory documentBuilderFactory = null; private DocumentBuilderFactory validatingDocumentBuilderFactory = null; private TransformerFactory transformerFactory = null; private Templates toStringTemplates = null; private XPathFactory xPathFactory = null; public Document newDocument() { return newDocument(null); } public Document newDocument(String root) { try { Document document = getDocumentBuilderFactory().newDocumentBuilder().newDocument(); document.setXmlVersion("1.0"); document.setXmlStandalone(true); if (root != null) newElement(document, root); return document; } catch (Exception e) { throw new RuntimeException(e); } } public Document getDocument(Node node) { return (node instanceof Document ? (Document)node : node.getOwnerDocument()); } public Element newElement(Node parent, String name) { return newElement(parent, name, null); } public Element newElement(Node parent, String name, String value) { Element element = getDocument(parent).createElement(name); parent.appendChild(element); if (value != null) element.setTextContent(value); return element; } public String getNormalizedValue(Node node) { if (node == null) return null; if (node.getNodeType() == Node.ELEMENT_NODE) { StringBuilder sb = new StringBuilder(); for (Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) { if (child.getNodeType() == Node.TEXT_NODE && child.getNodeValue() != null) { String value = child.getNodeValue().trim(); if (value.length() > 0) { if (sb.length() > 0) sb.append(' '); sb.append(value); } } } return sb.toString(); } return (node.getNodeValue() != null ? node.getNodeValue().trim() : null); } public String setValue(Node node, String value) { if (node != null) { String previousValue = getNormalizedValue(node); switch (node.getNodeType()) { case Node.ELEMENT_NODE: ((Element)node).setTextContent(value); break; case Node.ATTRIBUTE_NODE: case Node.TEXT_NODE: node.setNodeValue(value); break; default: throw new RuntimeException("Illegal node for write operations: " + node); } return previousValue; } return null; } public Document buildDocument(String xml) { try { DocumentBuilder builder = getDocumentBuilderFactory().newDocumentBuilder(); return builder.parse(new InputSource(new StringReader(xml))); } catch (Exception e) { throw new RuntimeException("Could not parse XML string", e); } } public Document loadDocument(InputStream input) throws IOException, SAXException { try { return getDocumentBuilderFactory().newDocumentBuilder().parse(input); } catch (ParserConfigurationException e) { throw new RuntimeException(e); } } public Document loadDocument(InputStream input, EntityResolver resolver, ErrorHandler errorHandler) throws IOException, SAXException { try { DocumentBuilder builder = getValidatingDocumentBuilderFactory().newDocumentBuilder(); builder.setEntityResolver(resolver); if (errorHandler != null) builder.setErrorHandler(errorHandler); return builder.parse(input); } catch (ParserConfigurationException e) { throw new RuntimeException(e); } } public void saveDocument(Document document, OutputStream output) { try { Transformer transformer = TransformerFactory.newInstance().newTransformer(); transformer.setOutputProperty(OutputKeys.METHOD, "xml"); transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); transformer.setOutputProperty(OutputKeys.INDENT, "no"); transformer.transform(new DOMSource(document), new StreamResult(output)); } catch (TransformerException e) { throw new RuntimeException("Could not save document", e); } } public String toString(Document doc) { try { Transformer transformer = getTransformerFactory().newTransformer(); StringWriter writer = new StringWriter(); transformer.transform(new DOMSource(doc), new StreamResult(writer)); return writer.toString(); } catch (Exception e) { throw new RuntimeException("Could not serialize document", e); } } protected Templates getToStringTemplates() { if (toStringTemplates == null) { try { toStringTemplates = TransformerFactory.newInstance().newTemplates(new StreamSource(new StringReader(TO_STRING_XSL))); } catch (Exception e) { throw new RuntimeException(e); } } return toStringTemplates; } public String toNodeString(Node node) { try { Transformer transformer = getToStringTemplates().newTransformer(); transformer.setOutputProperty(OutputKeys.METHOD, "xml"); transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); transformer.setOutputProperty(OutputKeys.ENCODING, Charset.defaultCharset().name()); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); StringWriter sw = new StringWriter(); transformer.transform(new DOMSource(node), new StreamResult(sw)); return sw.toString(); } catch (Exception e) { throw new RuntimeException(e); } } public Node selectSingleNode(Object context, String expression) { try { return (Node)getXPathFactory().newXPath().evaluate(expression, context, XPathConstants.NODE); } catch (Exception e) { throw new RuntimeException(e); } } public List<Node> selectNodeSet(Object context, String expression) { try { NodeList nodeList = (NodeList)getXPathFactory().newXPath().evaluate(expression, context, XPathConstants.NODESET); List<Node> nodes = new ArrayList<Node>(nodeList.getLength()); for (int i = 0; i < nodeList.getLength(); i++) nodes.add(nodeList.item(i)); return nodes; } catch (Exception e) { throw new RuntimeException(e); } } private DocumentBuilderFactory getDocumentBuilderFactory() { if (documentBuilderFactory == null) { try { documentBuilderFactory = DocumentBuilderFactory.newInstance(); documentBuilderFactory.setCoalescing(true); documentBuilderFactory.setIgnoringComments(true); } catch (Exception e) { throw new RuntimeException(e); } } return documentBuilderFactory; } private DocumentBuilderFactory getValidatingDocumentBuilderFactory() { if (validatingDocumentBuilderFactory == null) { try { validatingDocumentBuilderFactory = DocumentBuilderFactory.newInstance(); validatingDocumentBuilderFactory.setCoalescing(true); validatingDocumentBuilderFactory.setIgnoringComments(true); validatingDocumentBuilderFactory.setValidating(true); validatingDocumentBuilderFactory.setIgnoringElementContentWhitespace(true); } catch (Exception e) { throw new RuntimeException(e); } } return validatingDocumentBuilderFactory; } private TransformerFactory getTransformerFactory() { if (transformerFactory == null) transformerFactory = TransformerFactory.newInstance(); return transformerFactory; } private XPathFactory getXPathFactory() { if (xPathFactory == null) { try { xPathFactory = XPathFactory.newInstance(); } catch (Exception e) { try { // Fallback to xalan for Google App Engine Class<?> factoryClass = Thread.currentThread().getContextClassLoader().loadClass("org.apache.xpath.jaxp.XPathFactoryImpl"); Method m = factoryClass.getMethod("newInstance", String.class, String.class, ClassLoader.class); xPathFactory = (XPathFactory)m.invoke(null, XPathFactory.DEFAULT_OBJECT_MODEL_URI, "org.apache.xpath.jaxp.XPathFactoryImpl", null); } catch (Exception f) { throw new RuntimeException("XPathFactory could not be found", f); } } } return xPathFactory; } }