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