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}