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
021 package org.granite.util;
022
023 import java.io.IOException;
024 import java.io.InputStream;
025 import java.io.OutputStream;
026 import java.io.StringReader;
027 import java.io.StringWriter;
028 import java.lang.reflect.Method;
029 import java.nio.charset.Charset;
030 import java.util.ArrayList;
031 import java.util.List;
032
033 import javax.xml.parsers.DocumentBuilder;
034 import javax.xml.parsers.DocumentBuilderFactory;
035 import javax.xml.parsers.ParserConfigurationException;
036 import javax.xml.transform.OutputKeys;
037 import javax.xml.transform.Templates;
038 import javax.xml.transform.Transformer;
039 import javax.xml.transform.TransformerException;
040 import javax.xml.transform.TransformerFactory;
041 import javax.xml.transform.dom.DOMSource;
042 import javax.xml.transform.stream.StreamResult;
043 import javax.xml.transform.stream.StreamSource;
044 import javax.xml.xpath.XPathConstants;
045 import javax.xml.xpath.XPathExpressionException;
046 import javax.xml.xpath.XPathFactory;
047
048 import org.w3c.dom.Document;
049 import org.w3c.dom.Element;
050 import org.w3c.dom.Node;
051 import org.w3c.dom.NodeList;
052 import org.xml.sax.EntityResolver;
053 import org.xml.sax.ErrorHandler;
054 import org.xml.sax.SAXException;
055
056 /**
057 * @author Franck WOLFF
058 */
059 public class DOM {
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 final ErrorHandler errorHandler;
071
072 private DocumentBuilderFactory documentBuilderFactory = null;
073 private DocumentBuilderFactory validatingDocumentBuilderFactory = null;
074 private XPathFactory xPathFactory = null;
075 private Templates toStringTemplates = null;
076
077 public DOM() {
078 this(null);
079 }
080
081 public DOM(ErrorHandler errorHandler) {
082 this.errorHandler = errorHandler;
083 }
084
085 protected DocumentBuilderFactory getDocumentBuilderFactory() {
086 if (documentBuilderFactory == null) {
087 try {
088 documentBuilderFactory = DocumentBuilderFactory.newInstance();
089
090 documentBuilderFactory.setCoalescing(true);
091 documentBuilderFactory.setIgnoringComments(true);
092 } catch (Exception e) {
093 throw new RuntimeException(e);
094 }
095 }
096 return documentBuilderFactory;
097 }
098
099 protected DocumentBuilderFactory getValidatingDocumentBuilderFactory() {
100 if (validatingDocumentBuilderFactory == null) {
101 try {
102 validatingDocumentBuilderFactory = DocumentBuilderFactory.newInstance();
103 validatingDocumentBuilderFactory.setCoalescing(true);
104 validatingDocumentBuilderFactory.setIgnoringComments(true);
105 validatingDocumentBuilderFactory.setValidating(true);
106 validatingDocumentBuilderFactory.setIgnoringElementContentWhitespace(true);
107 } catch (Exception e) {
108 throw new RuntimeException(e);
109 }
110 }
111 return validatingDocumentBuilderFactory;
112 }
113
114 protected XPathFactory getXPathFactory() {
115 if (xPathFactory == null) {
116 try {
117 xPathFactory = XPathFactory.newInstance();
118 }
119 catch (Exception e) {
120 try {
121 // Fallback to xalan for Google App Engine
122 Class<?> factoryClass = Thread.currentThread().getContextClassLoader().loadClass("org.apache.xpath.jaxp.XPathFactoryImpl");
123 Method m = factoryClass.getMethod("newInstance", String.class, String.class, ClassLoader.class);
124 xPathFactory = (XPathFactory)m.invoke(null, XPathFactory.DEFAULT_OBJECT_MODEL_URI, "org.apache.xpath.jaxp.XPathFactoryImpl", null);
125 }
126 catch (Exception f) {
127 throw new RuntimeException("XPathFactory could not be found", f);
128 }
129 }
130 }
131 return xPathFactory;
132 }
133
134 protected Templates getToStringTemplates() {
135 if (toStringTemplates == null) {
136 try {
137 toStringTemplates = TransformerFactory.newInstance().newTemplates(new StreamSource(new StringReader(TO_STRING_XSL)));
138 } catch (Exception e) {
139 throw new RuntimeException(e);
140 }
141 }
142 return toStringTemplates;
143 }
144
145 public Document loadDocument(InputStream input) throws IOException, SAXException {
146 try {
147 return getDocumentBuilderFactory().newDocumentBuilder().parse(input);
148 } catch (ParserConfigurationException e) {
149 throw new RuntimeException(e);
150 }
151 }
152
153 public Document loadDocument(InputStream input, EntityResolver resolver) throws IOException, SAXException {
154 try {
155 DocumentBuilder builder = getValidatingDocumentBuilderFactory().newDocumentBuilder();
156 builder.setEntityResolver(resolver);
157 if (errorHandler != null)
158 builder.setErrorHandler(errorHandler);
159 return builder.parse(input);
160 } catch (ParserConfigurationException e) {
161 throw new RuntimeException(e);
162 }
163 }
164
165 public void saveDocument(Document document, OutputStream output) throws TransformerException {
166 Transformer transformer = TransformerFactory.newInstance().newTransformer();
167 transformer.setOutputProperty(OutputKeys.METHOD, "xml");
168 transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
169 transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
170 transformer.setOutputProperty(OutputKeys.INDENT, "no");
171
172 transformer.transform(new DOMSource(document), new StreamResult(output));
173 }
174
175 public Document newDocument() {
176 return newDocument(null);
177 }
178
179 public Document newDocument(String root) {
180 try {
181 Document document = getDocumentBuilderFactory().newDocumentBuilder().newDocument();
182 document.setXmlVersion("1.0");
183 document.setXmlStandalone(true);
184 if (root != null)
185 newElement(document, root);
186 return document;
187 } catch (ParserConfigurationException e) {
188 throw new RuntimeException(e);
189 }
190 }
191
192 public Document getDocument(Node node) {
193 return (node instanceof Document ? (Document)node : node.getOwnerDocument());
194 }
195
196 public Element newElement(Node parent, String name) {
197 return newElement(parent, name, null);
198 }
199
200 public Element newElement(Node parent, String name, String value) {
201 Element element = getDocument(parent).createElement(name);
202 parent.appendChild(element);
203 if (value != null)
204 element.setTextContent(value);
205 return element;
206 }
207
208 public String getNormalizedValue(Node node) {
209 if (node == null)
210 return null;
211 if (node.getNodeType() == Node.ELEMENT_NODE) {
212 StringBuilder sb = new StringBuilder();
213 for (Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
214 if (child.getNodeType() == Node.TEXT_NODE && child.getNodeValue() != null) {
215 String value = child.getNodeValue().trim();
216 if (value.length() > 0) {
217 if (sb.length() > 0)
218 sb.append(' ');
219 sb.append(value);
220 }
221 }
222 }
223 return sb.toString();
224 }
225 return (node.getNodeValue() != null ? node.getNodeValue().trim() : null);
226 }
227
228 public String setValue(Node node, String value) {
229 if (node != null) {
230 String previousValue = getNormalizedValue(node);
231 switch (node.getNodeType()) {
232 case Node.ELEMENT_NODE:
233 ((Element)node).setTextContent(value);
234 break;
235 case Node.ATTRIBUTE_NODE:
236 case Node.TEXT_NODE:
237 node.setNodeValue(value);
238 break;
239 default:
240 throw new RuntimeException("Illegal node for write operations: " + node);
241 }
242 return previousValue;
243 }
244 return null;
245 }
246
247 public Node selectSingleNode(Object context, String expression) throws XPathExpressionException {
248 return (Node)getXPathFactory().newXPath().evaluate(expression, context, XPathConstants.NODE);
249 }
250
251 public List<Node> selectNodes(Object context, String expression) throws XPathExpressionException {
252 NodeList nodeList = (NodeList)getXPathFactory().newXPath().evaluate(expression, context, XPathConstants.NODESET);
253 List<Node> nodes = new ArrayList<Node>(nodeList.getLength());
254 for (int i = 0; i < nodeList.getLength(); i++)
255 nodes.add(nodeList.item(i));
256 return nodes;
257 }
258
259 public String toString(Node node) {
260 try {
261 Transformer transformer = getToStringTemplates().newTransformer();
262 transformer.setOutputProperty(OutputKeys.METHOD, "xml");
263 transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
264 transformer.setOutputProperty(OutputKeys.ENCODING, Charset.defaultCharset().name());
265 transformer.setOutputProperty(OutputKeys.INDENT, "yes");
266 transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
267
268 StringWriter sw = new StringWriter();
269 transformer.transform(new DOMSource(node), new StreamResult(sw));
270 return sw.toString();
271 } catch (Exception e) {
272 throw new RuntimeException(e);
273 }
274
275 }
276 }