001package biweekly.util;
002
003import java.io.IOException;
004import java.io.InputStream;
005import java.io.Reader;
006import java.io.StringReader;
007import java.io.StringWriter;
008import java.io.Writer;
009import java.util.ArrayList;
010import java.util.HashMap;
011import java.util.List;
012import java.util.Map;
013
014import javax.xml.parsers.DocumentBuilder;
015import javax.xml.parsers.DocumentBuilderFactory;
016import javax.xml.parsers.ParserConfigurationException;
017import javax.xml.transform.Transformer;
018import javax.xml.transform.TransformerConfigurationException;
019import javax.xml.transform.TransformerException;
020import javax.xml.transform.TransformerFactory;
021import javax.xml.transform.TransformerFactoryConfigurationError;
022import javax.xml.transform.dom.DOMSource;
023import javax.xml.transform.stream.StreamResult;
024
025import org.w3c.dom.Document;
026import org.w3c.dom.Element;
027import org.w3c.dom.Node;
028import org.w3c.dom.NodeList;
029import org.xml.sax.InputSource;
030import org.xml.sax.SAXException;
031
032/*
033 Copyright (c) 2013, Michael Angstadt
034 All rights reserved.
035
036 Redistribution and use in source and binary forms, with or without
037 modification, are permitted provided that the following conditions are met: 
038
039 1. Redistributions of source code must retain the above copyright notice, this
040 list of conditions and the following disclaimer. 
041 2. Redistributions in binary form must reproduce the above copyright notice,
042 this list of conditions and the following disclaimer in the documentation
043 and/or other materials provided with the distribution. 
044
045 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
046 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
047 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
048 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
049 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
050 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
051 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
052 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
053 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
054 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
055
056 The views and conclusions contained in the software and documentation are those
057 of the authors and should not be interpreted as representing official policies, 
058 either expressed or implied, of the FreeBSD Project.
059 */
060
061/**
062 * Generic XML utility methods.
063 * @author Michael Angstadt
064 */
065public class XmlUtils {
066        /**
067         * Creates a new XML document.
068         * @return the XML document
069         */
070        public static Document createDocument() {
071                try {
072                        DocumentBuilderFactory fact = DocumentBuilderFactory.newInstance();
073                        fact.setNamespaceAware(true);
074                        DocumentBuilder db = fact.newDocumentBuilder();
075                        return db.newDocument();
076                } catch (ParserConfigurationException e) {
077                        //will probably never be thrown because we're not doing anything fancy with the configuration
078                        throw new RuntimeException(e);
079                }
080        }
081
082        /**
083         * Parses an XML string into a DOM.
084         * @param xml the XML string
085         * @return the parsed DOM
086         * @throws SAXException if the string is not valid XML
087         */
088        public static Document toDocument(String xml) throws SAXException {
089                try {
090                        return toDocument(new StringReader(xml));
091                } catch (IOException e) {
092                        //reading from string
093                        throw new RuntimeException(e);
094                }
095        }
096
097        /**
098         * Parses an XML document from an input stream.
099         * @param in the input stream
100         * @return the parsed DOM
101         * @throws SAXException if the XML is not valid
102         * @throws IOException if there is a problem reading from the input stream
103         */
104        public static Document toDocument(InputStream in) throws SAXException, IOException {
105                return toDocument(new InputSource(in));
106        }
107
108        /**
109         * <p>
110         * Parses an XML document from a reader.
111         * </p>
112         * <p>
113         * Note that use of this method is discouraged. It ignores the character
114         * encoding that is defined within the XML document itself, and should only
115         * be used if the encoding is undefined or if the encoding needs to be
116         * ignored for whatever reason. The {@link #toDocument(InputStream)} method
117         * should be used instead, since it takes the XML document's character
118         * encoding into account when parsing.
119         * </p>
120         * @param reader the reader
121         * @return the parsed DOM
122         * @throws SAXException if the XML is not valid
123         * @throws IOException if there is a problem reading from the reader
124         * @see <a
125         * href="http://stackoverflow.com/q/3482494/13379">http://stackoverflow.com/q/3482494/13379</a>
126         */
127        public static Document toDocument(Reader reader) throws SAXException, IOException {
128                return toDocument(new InputSource(reader));
129        }
130
131        private static Document toDocument(InputSource in) throws SAXException, IOException {
132                try {
133                        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
134                        dbf.setNamespaceAware(true);
135                        dbf.setIgnoringComments(true);
136                        DocumentBuilder db = dbf.newDocumentBuilder();
137                        return db.parse(in);
138                } catch (ParserConfigurationException e) {
139                        //will probably never be thrown because we're not doing anything fancy with the configuration
140                        throw new RuntimeException(e);
141                }
142        }
143
144        /**
145         * Converts an XML node to a string.
146         * @param node the XML node
147         * @return the string
148         */
149        public static String toString(Node node) {
150                return toString(node, new HashMap<String, String>());
151        }
152
153        /**
154         * Converts an XML node to a string.
155         * @param node the XML node
156         * @param outputProperties the output properties
157         * @return the string
158         */
159        public static String toString(Node node, Map<String, String> outputProperties) {
160                try {
161                        StringWriter writer = new StringWriter();
162                        toWriter(node, writer, outputProperties);
163                        return writer.toString();
164                } catch (TransformerException e) {
165                        //should never be thrown because we're writing to string
166                        throw new RuntimeException(e);
167                }
168        }
169
170        /**
171         * Writes an XML node to a writer.
172         * @param node the XML node
173         * @param writer the writer
174         * @throws TransformerException if there's a problem writing to the writer
175         */
176        public static void toWriter(Node node, Writer writer) throws TransformerException {
177                toWriter(node, writer, new HashMap<String, String>());
178        }
179
180        /**
181         * Writes an XML node to a writer.
182         * @param node the XML node
183         * @param writer the writer
184         * @param outputProperties the output properties
185         * @throws TransformerException if there's a problem writing to the writer
186         */
187        public static void toWriter(Node node, Writer writer, Map<String, String> outputProperties) throws TransformerException {
188                try {
189                        Transformer transformer = TransformerFactory.newInstance().newTransformer();
190                        for (Map.Entry<String, String> property : outputProperties.entrySet()) {
191                                try {
192                                        transformer.setOutputProperty(property.getKey(), property.getValue());
193                                } catch (IllegalArgumentException e) {
194                                        //ignore invalid output properties
195                                }
196                        }
197
198                        DOMSource source = new DOMSource(node);
199                        StreamResult result = new StreamResult(writer);
200                        transformer.transform(source, result);
201                } catch (TransformerConfigurationException e) {
202                        //no complex configurations
203                } catch (TransformerFactoryConfigurationError e) {
204                        //no complex configurations
205                }
206        }
207
208        /**
209         * Gets all the elements out of a {@link NodeList}.
210         * @param nodeList the node list
211         * @return the elements
212         */
213        public static List<Element> toElementList(NodeList nodeList) {
214                List<Element> elements = new ArrayList<Element>();
215                for (int i = 0; i < nodeList.getLength(); i++) {
216                        Node node = nodeList.item(i);
217                        if (node instanceof Element) {
218                                elements.add((Element) node);
219                        }
220                }
221                return elements;
222        }
223
224        /**
225         * Gets the root element of a document.
226         * @param parent the document
227         * @return the root element
228         */
229        public static Element getRootElement(Document parent) {
230                return getFirstChildElement((Node) parent);
231        }
232
233        /**
234         * Gets the first child element of an element.
235         * @param parent the parent element
236         * @return the first child element or null if there are no child elements
237         */
238        public static Element getFirstChildElement(Element parent) {
239                return getFirstChildElement((Node) parent);
240        }
241
242        /**
243         * Gets the first child element of a node.
244         * @param parent the node
245         * @return the first child element or null if there are no child elements
246         */
247        private static Element getFirstChildElement(Node parent) {
248                NodeList nodeList = parent.getChildNodes();
249                for (int i = 0; i < nodeList.getLength(); i++) {
250                        Node node = nodeList.item(i);
251                        if (node instanceof Element) {
252                                return (Element) node;
253                        }
254                }
255                return null;
256        }
257
258        private XmlUtils() {
259                //hide
260        }
261}