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