001/* 002 GRANITE DATA SERVICES 003 Copyright (C) 2011 GRANITE DATA SERVICES S.A.S. 004 005 This file is part of Granite Data Services. 006 007 Granite Data Services is free software; you can redistribute it and/or modify 008 it under the terms of the GNU Library General Public License as published by 009 the Free Software Foundation; either version 2 of the License, or (at your 010 option) any later version. 011 012 Granite Data Services is distributed in the hope that it will be useful, but 013 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 014 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License 015 for more details. 016 017 You should have received a copy of the GNU Library General Public License 018 along with this library; if not, see <http://www.gnu.org/licenses/>. 019*/ 020 021package org.granite.util; 022 023import java.io.IOException; 024import java.io.InputStream; 025import java.io.OutputStream; 026import java.io.StringReader; 027import java.io.StringWriter; 028import java.lang.reflect.Method; 029import java.nio.charset.Charset; 030import java.util.ArrayList; 031import java.util.List; 032 033import javax.xml.parsers.DocumentBuilder; 034import javax.xml.parsers.DocumentBuilderFactory; 035import javax.xml.parsers.ParserConfigurationException; 036import javax.xml.transform.OutputKeys; 037import javax.xml.transform.Templates; 038import javax.xml.transform.Transformer; 039import javax.xml.transform.TransformerException; 040import javax.xml.transform.TransformerFactory; 041import javax.xml.transform.dom.DOMSource; 042import javax.xml.transform.stream.StreamResult; 043import javax.xml.transform.stream.StreamSource; 044import javax.xml.xpath.XPathConstants; 045import javax.xml.xpath.XPathFactory; 046 047import org.w3c.dom.Document; 048import org.w3c.dom.Element; 049import org.w3c.dom.Node; 050import org.w3c.dom.NodeList; 051import org.xml.sax.EntityResolver; 052import org.xml.sax.ErrorHandler; 053import org.xml.sax.InputSource; 054import org.xml.sax.SAXException; 055 056/** 057 * @author Franck WOLFF 058 */ 059public class StdXMLUtil implements XMLUtil { 060 061 protected static final String TO_STRING_XSL = 062 "<?xml version='1.0' encoding='UTF-8'?>" + 063 "<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>" + 064 " <xsl:strip-space elements='*'/>" + 065 " <xsl:template match='/'>" + 066 " <xsl:copy-of select='*'/>" + 067 " </xsl:template>" + 068 "</xsl:stylesheet>"; 069 070 private DocumentBuilderFactory documentBuilderFactory = null; 071 private DocumentBuilderFactory validatingDocumentBuilderFactory = null; 072 private TransformerFactory transformerFactory = null; 073 private Templates toStringTemplates = null; 074 private XPathFactory xPathFactory = null; 075 076 077 public Document newDocument() { 078 return newDocument(null); 079 } 080 081 public Document newDocument(String root) { 082 try { 083 Document document = getDocumentBuilderFactory().newDocumentBuilder().newDocument(); 084 document.setXmlVersion("1.0"); 085 document.setXmlStandalone(true); 086 if (root != null) 087 newElement(document, root); 088 return document; 089 } 090 catch (Exception e) { 091 throw new RuntimeException(e); 092 } 093 } 094 095 public Document getDocument(Node node) { 096 return (node instanceof Document ? (Document)node : node.getOwnerDocument()); 097 } 098 099 public Element newElement(Node parent, String name) { 100 return newElement(parent, name, null); 101 } 102 103 public Element newElement(Node parent, String name, String value) { 104 Element element = getDocument(parent).createElement(name); 105 parent.appendChild(element); 106 if (value != null) 107 element.setTextContent(value); 108 return element; 109 } 110 111 public String getNormalizedValue(Node node) { 112 if (node == null) 113 return null; 114 if (node.getNodeType() == Node.ELEMENT_NODE) { 115 StringBuilder sb = new StringBuilder(); 116 for (Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) { 117 if (child.getNodeType() == Node.TEXT_NODE && child.getNodeValue() != null) { 118 String value = child.getNodeValue().trim(); 119 if (value.length() > 0) { 120 if (sb.length() > 0) 121 sb.append(' '); 122 sb.append(value); 123 } 124 } 125 } 126 return sb.toString(); 127 } 128 return (node.getNodeValue() != null ? node.getNodeValue().trim() : null); 129 } 130 131 public String setValue(Node node, String value) { 132 if (node != null) { 133 String previousValue = getNormalizedValue(node); 134 switch (node.getNodeType()) { 135 case Node.ELEMENT_NODE: 136 ((Element)node).setTextContent(value); 137 break; 138 case Node.ATTRIBUTE_NODE: 139 case Node.TEXT_NODE: 140 node.setNodeValue(value); 141 break; 142 default: 143 throw new RuntimeException("Illegal node for write operations: " + node); 144 } 145 return previousValue; 146 } 147 return null; 148 } 149 150 public Document buildDocument(String xml) { 151 try { 152 DocumentBuilder builder = getDocumentBuilderFactory().newDocumentBuilder(); 153 return builder.parse(new InputSource(new StringReader(xml))); 154 } catch (Exception e) { 155 throw new RuntimeException("Could not parse XML string", e); 156 } 157 } 158 159 public Document loadDocument(InputStream input) throws IOException, SAXException { 160 try { 161 return getDocumentBuilderFactory().newDocumentBuilder().parse(input); 162 } catch (ParserConfigurationException e) { 163 throw new RuntimeException(e); 164 } 165 } 166 167 public Document loadDocument(InputStream input, EntityResolver resolver, ErrorHandler errorHandler) throws IOException, SAXException { 168 try { 169 DocumentBuilder builder = getValidatingDocumentBuilderFactory().newDocumentBuilder(); 170 builder.setEntityResolver(resolver); 171 if (errorHandler != null) 172 builder.setErrorHandler(errorHandler); 173 return builder.parse(input); 174 } catch (ParserConfigurationException e) { 175 throw new RuntimeException(e); 176 } 177 } 178 179 public void saveDocument(Document document, OutputStream output) { 180 try { 181 Transformer transformer = TransformerFactory.newInstance().newTransformer(); 182 transformer.setOutputProperty(OutputKeys.METHOD, "xml"); 183 transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); 184 transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); 185 transformer.setOutputProperty(OutputKeys.INDENT, "no"); 186 187 transformer.transform(new DOMSource(document), new StreamResult(output)); 188 } 189 catch (TransformerException e) { 190 throw new RuntimeException("Could not save document", e); 191 } 192 } 193 194 public String toString(Document doc) { 195 try { 196 Transformer transformer = getTransformerFactory().newTransformer(); 197 StringWriter writer = new StringWriter(); 198 transformer.transform(new DOMSource(doc), new StreamResult(writer)); 199 return writer.toString(); 200 } catch (Exception e) { 201 throw new RuntimeException("Could not serialize document", e); 202 } 203 } 204 205 protected Templates getToStringTemplates() { 206 if (toStringTemplates == null) { 207 try { 208 toStringTemplates = TransformerFactory.newInstance().newTemplates(new StreamSource(new StringReader(TO_STRING_XSL))); 209 } catch (Exception e) { 210 throw new RuntimeException(e); 211 } 212 } 213 return toStringTemplates; 214 } 215 216 public String toNodeString(Node node) { 217 try { 218 Transformer transformer = getToStringTemplates().newTransformer(); 219 transformer.setOutputProperty(OutputKeys.METHOD, "xml"); 220 transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); 221 transformer.setOutputProperty(OutputKeys.ENCODING, Charset.defaultCharset().name()); 222 transformer.setOutputProperty(OutputKeys.INDENT, "yes"); 223 transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); 224 225 StringWriter sw = new StringWriter(); 226 transformer.transform(new DOMSource(node), new StreamResult(sw)); 227 return sw.toString(); 228 } catch (Exception e) { 229 throw new RuntimeException(e); 230 } 231 232 } 233 234 public Node selectSingleNode(Object context, String expression) { 235 try { 236 return (Node)getXPathFactory().newXPath().evaluate(expression, context, XPathConstants.NODE); 237 } catch (Exception e) { 238 throw new RuntimeException(e); 239 } 240 } 241 242 public List<Node> selectNodeSet(Object context, String expression) { 243 try { 244 NodeList nodeList = (NodeList)getXPathFactory().newXPath().evaluate(expression, context, XPathConstants.NODESET); 245 List<Node> nodes = new ArrayList<Node>(nodeList.getLength()); 246 for (int i = 0; i < nodeList.getLength(); i++) 247 nodes.add(nodeList.item(i)); 248 return nodes; 249 } catch (Exception e) { 250 throw new RuntimeException(e); 251 } 252 } 253 254 255 private DocumentBuilderFactory getDocumentBuilderFactory() { 256 if (documentBuilderFactory == null) { 257 try { 258 documentBuilderFactory = DocumentBuilderFactory.newInstance(); 259 260 documentBuilderFactory.setCoalescing(true); 261 documentBuilderFactory.setIgnoringComments(true); 262 } 263 catch (Exception e) { 264 throw new RuntimeException(e); 265 } 266 } 267 return documentBuilderFactory; 268 } 269 270 private DocumentBuilderFactory getValidatingDocumentBuilderFactory() { 271 if (validatingDocumentBuilderFactory == null) { 272 try { 273 validatingDocumentBuilderFactory = DocumentBuilderFactory.newInstance(); 274 validatingDocumentBuilderFactory.setCoalescing(true); 275 validatingDocumentBuilderFactory.setIgnoringComments(true); 276 validatingDocumentBuilderFactory.setValidating(true); 277 validatingDocumentBuilderFactory.setIgnoringElementContentWhitespace(true); 278 } catch (Exception e) { 279 throw new RuntimeException(e); 280 } 281 } 282 return validatingDocumentBuilderFactory; 283 } 284 285 private TransformerFactory getTransformerFactory() { 286 if (transformerFactory == null) 287 transformerFactory = TransformerFactory.newInstance(); 288 return transformerFactory; 289 } 290 291 private XPathFactory getXPathFactory() { 292 if (xPathFactory == null) { 293 try { 294 xPathFactory = XPathFactory.newInstance(); 295 } 296 catch (Exception e) { 297 try { 298 // Fallback to xalan for Google App Engine 299 Class<?> factoryClass = Thread.currentThread().getContextClassLoader().loadClass("org.apache.xpath.jaxp.XPathFactoryImpl"); 300 Method m = factoryClass.getMethod("newInstance", String.class, String.class, ClassLoader.class); 301 xPathFactory = (XPathFactory)m.invoke(null, XPathFactory.DEFAULT_OBJECT_MODEL_URI, "org.apache.xpath.jaxp.XPathFactoryImpl", null); 302 } 303 catch (Exception f) { 304 throw new RuntimeException("XPathFactory could not be found", f); 305 } 306 } 307 } 308 return xPathFactory; 309 } 310 311}