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