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