(no commit message)
[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
43 import org.w3c.dom.Attr;
44 import org.w3c.dom.Document;
45 import org.w3c.dom.Element;
46 import org.w3c.dom.NamedNodeMap;
47 import org.w3c.dom.Node;
48 import org.w3c.dom.NodeList;
49 import org.xml.sax.SAXException;
50
51 /**
52  * Some basic XML utilities for common reoccuring tasks for DOM documents.
53  * 
54  * @author Erik Brakkee
55  */
56 public final class DomUtils {
57     private static final Logger LOG = Logger
58         .getLogger(DomUtils.class.getName());
59
60     /**
61      * Disabled default constructor.
62      * 
63      */
64     private DomUtils() {
65         // Empty.
66     }
67
68     /**
69      * Parses an XML document from a string.
70      * 
71      * @param aDocument
72      *            document.
73      * 
74      * @return
75      * 
76      */
77     public static Document read(String aDocument) throws XMLException {
78         ByteArrayInputStream is = new ByteArrayInputStream(aDocument.getBytes());
79
80         return read(is);
81     }
82
83     /**
84      * Parses an XML document from a stream.
85      * 
86      * @param aIs
87      *            Input stream.
88      * 
89      * @return
90      * 
91      */
92     public static Document read(InputStream aIs) throws XMLException {
93         try {
94             DocumentBuilder builder = DocumentBuilderFactory.newInstance()
95                 .newDocumentBuilder();
96
97             return builder.parse(aIs);
98         } catch (SAXException e) {
99             throw new XMLException(e.getMessage(), e);
100         } catch (IOException e) {
101             throw new XMLException(e.getMessage(), e);
102         } catch (ParserConfigurationException e) {
103             throw new XMLException(e.getMessage(), e);
104         } finally {
105             try {
106                 aIs.close();
107             } catch (Exception e) {
108                 LOG.log(Level.WARNING, "Error closing XML file", e);
109             }
110         }
111     }
112
113     /**
114      * Reads and validates a document against a schema.
115      * 
116      * @param aIs
117      *            Input stream.
118      * @param aSchema
119      *            Schema.
120      * 
121      * @return Parsed and validated document.
122      * 
123      */
124     public static Document readAndValidate(InputStream aIs, InputStream aSchema)
125         throws XMLException {
126         try {
127             final Schema schema = SchemaFactory.newInstance(
128                 XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema(
129                 new StreamSource(aSchema));
130
131             final DocumentBuilderFactory factory = DocumentBuilderFactory
132                 .newInstance();
133             factory.setValidating(true);
134             factory.setNamespaceAware(true);
135             factory.setSchema(schema);
136
137             return factory.newDocumentBuilder().parse(aIs);
138         } catch (SAXException e) {
139             throw new XMLException(e.getMessage(), e);
140         } catch (IOException e) {
141             throw new XMLException(e.getMessage(), e);
142         } catch (ParserConfigurationException e) {
143             throw new XMLException(e.getMessage(), e);
144         } finally {
145             try {
146                 aSchema.close();
147             } catch (Exception e) {
148                 LOG.log(Level.WARNING, "Error closing schema", e);
149             }
150
151             try {
152                 aIs.close();
153             } catch (Exception e) {
154                 LOG.log(Level.WARNING, "Error closing XML file", e);
155             }
156         }
157     }
158
159     /**
160      * Serializes an XML document to a stream.
161      * 
162      * @param aDocument
163      *            Document to serialize.
164      * @param aOs
165      *            Output stream.
166      * 
167      */
168     public static void serialize(Document aDocument, OutputStream aOs)
169         throws IOException {
170         try {
171             TransformerFactory factory = TransformerFactory.newInstance();
172             Transformer identityTransform = factory.newTransformer();
173             DOMSource source = new DOMSource(aDocument);
174             StreamResult result = new StreamResult(aOs);
175             identityTransform.transform(source, result);
176         } catch (TransformerException e) {
177             throw new IOException(e.getMessage(), e);
178         }
179     }
180
181     /**
182      * Serializes an XML document.
183      * 
184      * @param aDocument
185      *            Document to serialize.
186      * 
187      * @return Serialized document.
188      * 
189      */
190     public static String serialize(Document aDocument) throws IOException {
191         ByteArrayOutputStream os = new ByteArrayOutputStream();
192         serialize(aDocument, os);
193
194         return os.toString();
195     }
196
197     /**
198      * Removes duplicate attributes from a DOM tree.This is useful for
199      * postprocessing the output of JTidy as a workaround for a bug in JTidy.
200      * 
201      * @param aNode
202      *            Node to remove duplicate attributes from (recursively).
203      *            Attributes of the node itself are not dealt with. Only the
204      *            child nodes are dealt with.
205      */
206     public static void removeDuplicateAttributes(Node aNode) {
207         NodeList list = aNode.getChildNodes();
208
209         for (int i = 0; i < list.getLength(); i++) {
210             Node node = list.item(i);
211
212             if (node instanceof Element) {
213                 removeDuplicateAttributes((Element) node);
214                 removeDuplicateAttributes(node);
215             }
216         }
217     }
218
219     /**
220      * Removes duplicate attributes from an element.
221      * 
222      * @param aElement
223      *            Element.
224      */
225     private static void removeDuplicateAttributes(Element aElement) {
226         NamedNodeMap attributes = aElement.getAttributes();
227         Map<String, Attr> uniqueAttributes = new TreeMap<String, Attr>();
228         List<Attr> attlist = new ArrayList<Attr>();
229
230         for (int i = 0; i < attributes.getLength(); i++) {
231             Attr attribute = (Attr) attributes.item(i);
232
233             if (uniqueAttributes.containsKey(attribute.getNodeName())) {
234                 LOG.info("Detected duplicate attribute (will be removed)'" +
235                     attribute.getNodeName() + "'");
236             }
237
238             uniqueAttributes.put(attribute.getNodeName(), attribute);
239             attlist.add(attribute);
240         }
241
242         // Remove all attributes from the element.
243         for (Attr att : attlist) {
244             aElement.removeAttributeNode(att);
245         }
246
247         // Add the unique attributes back to the element.
248         for (Attr att : uniqueAttributes.values()) {
249             aElement.setAttributeNode(att);
250         }
251     }
252 }