001    package ezvcard.util;
002    
003    import java.io.IOException;
004    import java.io.InputStream;
005    import java.io.Reader;
006    import java.io.StringReader;
007    import java.io.StringWriter;
008    import java.io.Writer;
009    import java.util.ArrayList;
010    import java.util.HashMap;
011    import java.util.List;
012    import java.util.Map;
013    
014    import javax.xml.parsers.DocumentBuilder;
015    import javax.xml.parsers.DocumentBuilderFactory;
016    import javax.xml.parsers.ParserConfigurationException;
017    import javax.xml.transform.Transformer;
018    import javax.xml.transform.TransformerConfigurationException;
019    import javax.xml.transform.TransformerException;
020    import javax.xml.transform.TransformerFactory;
021    import javax.xml.transform.TransformerFactoryConfigurationError;
022    import javax.xml.transform.dom.DOMSource;
023    import javax.xml.transform.stream.StreamResult;
024    
025    import org.w3c.dom.Document;
026    import org.w3c.dom.Element;
027    import org.w3c.dom.Node;
028    import org.w3c.dom.NodeList;
029    import org.xml.sax.InputSource;
030    import 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     */
065    public 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 a 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    }