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