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.XPathFactory;
046
047 import org.w3c.dom.Document;
048 import org.w3c.dom.Element;
049 import org.w3c.dom.Node;
050 import org.w3c.dom.NodeList;
051 import org.xml.sax.EntityResolver;
052 import org.xml.sax.ErrorHandler;
053 import org.xml.sax.InputSource;
054 import org.xml.sax.SAXException;
055
056 /**
057 * @author Franck WOLFF
058 */
059 public 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 }