a0a118c1062cd82e8fd0aac29bb84fb01a371fb3
[utils] / support / general / src / main / java / org / wamblee / xml / DomUtils.java
1 /*
2  * Copyright 2005-2010 the original author or authors.
3  * 
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  * 
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  * 
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package org.wamblee.xml;
17
18 import java.io.ByteArrayInputStream;
19 import java.io.ByteArrayOutputStream;
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.io.OutputStream;
23 import java.util.ArrayList;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.TreeMap;
27 import java.util.logging.Level;
28 import java.util.logging.Logger;
29
30 import javax.xml.XMLConstants;
31 import javax.xml.parsers.DocumentBuilder;
32 import javax.xml.parsers.DocumentBuilderFactory;
33 import javax.xml.parsers.ParserConfigurationException;
34 import javax.xml.transform.Transformer;
35 import javax.xml.transform.TransformerException;
36 import javax.xml.transform.TransformerFactory;
37 import javax.xml.transform.dom.DOMSource;
38 import javax.xml.transform.stream.StreamResult;
39 import javax.xml.transform.stream.StreamSource;
40 import javax.xml.validation.Schema;
41 import javax.xml.validation.SchemaFactory;
42 import javax.xml.validation.Validator;
43
44 import org.w3c.dom.Attr;
45 import org.w3c.dom.Document;
46 import org.w3c.dom.Element;
47 import org.w3c.dom.NamedNodeMap;
48 import org.w3c.dom.Node;
49 import org.w3c.dom.NodeList;
50 import org.w3c.dom.bootstrap.DOMImplementationRegistry;
51 import org.w3c.dom.ls.DOMImplementationLS;
52 import org.w3c.dom.ls.LSException;
53 import org.w3c.dom.ls.LSInput;
54 import org.w3c.dom.ls.LSParser;
55 import org.xml.sax.SAXException;
56
57 /**
58  * Some basic XML utilities for common reoccuring tasks for DOM documents.
59  * 
60  * @author Erik Brakkee
61  */
62 public final class DomUtils {
63     private static final Logger LOG = Logger
64         .getLogger(DomUtils.class.getName());
65
66     /**
67      * Disabled default constructor.
68      * 
69      */
70     private DomUtils() {
71         // Empty.
72     }
73
74     /**
75      * Parses an XML document from a string.
76      * 
77      * @param aDocument
78      *            document.
79      * 
80      * @return
81      * 
82      */
83     public static Document read(String aDocument) throws XMLException {
84         ByteArrayInputStream is = new ByteArrayInputStream(aDocument.getBytes());
85
86         return read(is);
87     }
88
89     /**
90      * Parses an XML document from a stream.
91      * 
92      * @param aIs
93      *            Input stream.
94      * 
95      * @return
96      * 
97      */
98     public static Document read(InputStream aIs) throws XMLException {
99         try {
100             DOMImplementationLS impl = getDomImplementationLS();
101
102             LSParser builder = impl.createLSParser(
103                 DOMImplementationLS.MODE_SYNCHRONOUS, null);
104             LSInput input = impl.createLSInput();
105             input.setByteStream(aIs);
106             return builder.parse(input);
107         } catch (LSException e) {
108             throw new XMLException(e.getMessage(), e);
109         } finally {
110             try {
111                 aIs.close();
112             } catch (Exception e) {
113                 LOG.log(Level.WARNING, "Error closing XML file", e);
114             }
115         }
116     }
117
118     /**
119      * Gets a dom level 3 implementation. 
120      * @return Dom implementation.
121      * @throws ClassNotFoundException
122      * @throws InstantiationException
123      * @throws IllegalAccessException
124      */
125     public static DOMImplementationLS getDomImplementationLS() {
126         final String message = "Could not get Dom level 3 implementation";
127         try {
128             DOMImplementationRegistry registry = DOMImplementationRegistry
129                 .newInstance();
130
131             DOMImplementationLS impl = (DOMImplementationLS) registry
132                 .getDOMImplementation("LS");
133             return impl;
134         } catch (ClassCastException e) {
135             throw new RuntimeException(message, e);
136         } catch (ClassNotFoundException e) {
137             throw new RuntimeException(message, e);
138         } catch (InstantiationException e) {
139             throw new RuntimeException(message, e);
140         } catch (IllegalAccessException e) {
141             throw new RuntimeException(message, e);
142         }
143     }
144
145     /**
146      * Reads and validates a document against a schema.
147      * 
148      * @param aIs
149      *            Input stream.
150      * @param aSchema
151      *            Schema.
152      * 
153      * @return Parsed and validated document.
154      * 
155      */
156     public static Document readAndValidate(InputStream aIs, InputStream aSchema)
157         throws XMLException {
158         try {
159             Document doc = read(aIs);
160             final Schema schema = SchemaFactory.newInstance(
161                 XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema(
162                 new StreamSource(aSchema));
163             Validator validator = schema.newValidator();
164             validator.validate(new DOMSource(doc));
165
166             return doc;
167         } catch (SAXException e) {
168             throw new XMLException(e.getMessage(), e);
169         } catch (IOException e) {
170             throw new XMLException(e.getMessage(), e);
171         } finally {
172             try {
173                 aSchema.close();
174             } catch (Exception e) {
175                 LOG.log(Level.WARNING, "Error closing schema", e);
176             }
177
178             try {
179                 aIs.close();
180             } catch (Exception e) {
181                 LOG.log(Level.WARNING, "Error closing XML file", e);
182             }
183         }
184     }
185
186     /**
187      * Serializes an XML document to a stream.
188      * 
189      * @param aDocument
190      *            Document to serialize.
191      * @param aOs
192      *            Output stream.
193      * 
194      */
195     public static void serialize(Document aDocument, OutputStream aOs)
196         throws IOException {
197         try {
198             TransformerFactory factory = TransformerFactory.newInstance();
199             Transformer identityTransform = factory.newTransformer();
200             DOMSource source = new DOMSource(aDocument);
201             StreamResult result = new StreamResult(aOs);
202             identityTransform.transform(source, result);
203         } catch (TransformerException e) {
204             throw new IOException(e.getMessage(), e);
205         }
206     }
207
208     /**
209      * Serializes an XML document.
210      * 
211      * @param aDocument
212      *            Document to serialize.
213      * 
214      * @return Serialized document.
215      * 
216      */
217     public static String serialize(Document aDocument) throws IOException {
218         ByteArrayOutputStream os = new ByteArrayOutputStream();
219         serialize(aDocument, os);
220
221         return os.toString();
222     }
223
224     /**
225      * Removes duplicate attributes from a DOM tree.This is useful for
226      * postprocessing the output of JTidy as a workaround for a bug in JTidy.
227      * 
228      * @param aNode
229      *            Node to remove duplicate attributes from (recursively).
230      *            Attributes of the node itself are not dealt with. Only the
231      *            child nodes are dealt with.
232      */
233     public static void removeDuplicateAttributes(Node aNode) {
234         NodeList list = aNode.getChildNodes();
235
236         for (int i = 0; i < list.getLength(); i++) {
237             Node node = list.item(i);
238
239             if (node instanceof Element) {
240                 removeDuplicateAttributes((Element) node);
241                 removeDuplicateAttributes(node);
242             }
243         }
244     }
245
246     /**
247      * Removes duplicate attributes from an element.
248      * 
249      * @param aElement
250      *            Element.
251      */
252     private static void removeDuplicateAttributes(Element aElement) {
253         NamedNodeMap attributes = aElement.getAttributes();
254         Map<String, Attr> uniqueAttributes = new TreeMap<String, Attr>();
255         List<Attr> attlist = new ArrayList<Attr>();
256
257         for (int i = 0; i < attributes.getLength(); i++) {
258             Attr attribute = (Attr) attributes.item(i);
259
260             if (uniqueAttributes.containsKey(attribute.getNodeName())) {
261                 LOG.info("Detected duplicate attribute (will be removed)'" +
262                     attribute.getNodeName() + "'");
263             }
264
265             uniqueAttributes.put(attribute.getNodeName(), attribute);
266             attlist.add(attribute);
267         }
268
269         // Remove all attributes from the element.
270         for (Attr att : attlist) {
271             aElement.removeAttributeNode(att);
272         }
273
274         // Add the unique attributes back to the element.
275         for (Attr att : uniqueAttributes.values()) {
276             aElement.setAttributeNode(att);
277         }
278     }
279 }