XmlUtils.java
1 /* 2 * Copyright (C) 2010 Ryszard Wiśniewski <brut.alll@gmail.com> 3 * Copyright (C) 2010 Connor Tumbleson <connor.tumbleson@gmail.com> 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * https://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package brut.xml; 18 19 import org.w3c.dom.Document; 20 import org.w3c.dom.Node; 21 import org.w3c.dom.NodeList; 22 import org.xml.sax.SAXException; 23 24 import javax.xml.XMLConstants; 25 import javax.xml.namespace.NamespaceContext; 26 import javax.xml.namespace.QName; 27 import javax.xml.parsers.DocumentBuilder; 28 import javax.xml.parsers.DocumentBuilderFactory; 29 import javax.xml.parsers.ParserConfigurationException; 30 import javax.xml.transform.OutputKeys; 31 import javax.xml.transform.Transformer; 32 import javax.xml.transform.TransformerException; 33 import javax.xml.transform.TransformerFactory; 34 import javax.xml.transform.dom.DOMSource; 35 import javax.xml.transform.stream.StreamResult; 36 import javax.xml.xpath.XPath; 37 import javax.xml.xpath.XPathConstants; 38 import javax.xml.xpath.XPathExpressionException; 39 import javax.xml.xpath.XPathFactory; 40 import java.io.ByteArrayInputStream; 41 import java.io.File; 42 import java.io.InputStream; 43 import java.io.IOException; 44 import java.io.OutputStream; 45 import java.nio.charset.StandardCharsets; 46 import java.nio.file.Files; 47 import java.util.Collections; 48 import java.util.Iterator; 49 import java.util.logging.Logger; 50 51 public final class XmlUtils { 52 private static final Logger LOGGER = Logger.getLogger(""); 53 54 private static final String FEATURE_DISALLOW_DOCTYPE_DECL = "http://apache.org/xml/features/disallow-doctype-decl"; 55 private static final String FEATURE_LOAD_EXTERNAL_DTD = "http://apache.org/xml/features/nonvalidating/load-external-dtd"; 56 57 private XmlUtils() { 58 // Private constructor for utility class 59 } 60 61 private static DocumentBuilder newDocumentBuilder(boolean nsAware) 62 throws SAXException, ParserConfigurationException { 63 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 64 factory.setNamespaceAware(nsAware); 65 factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); 66 factory.setFeature(FEATURE_DISALLOW_DOCTYPE_DECL, true); 67 factory.setFeature(FEATURE_LOAD_EXTERNAL_DTD, false); 68 69 try { 70 factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); 71 factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); 72 } catch (IllegalArgumentException ex) { 73 LOGGER.warning("JAXP 1.5 Support is required to validate XML"); 74 } 75 76 return factory.newDocumentBuilder(); 77 } 78 79 public static Document newDocument() throws SAXException, ParserConfigurationException { 80 return newDocument(false); 81 } 82 83 public static Document newDocument(boolean nsAware) throws SAXException, ParserConfigurationException { 84 return newDocumentBuilder(nsAware).newDocument(); 85 } 86 87 public static Document loadDocument(File file) 88 throws IOException, SAXException, ParserConfigurationException { 89 return loadDocument(file, false); 90 } 91 92 public static Document loadDocumentContent(String content, boolean nsAware) 93 throws IOException, SAXException, ParserConfigurationException { 94 DocumentBuilder builder = newDocumentBuilder(nsAware); 95 try (InputStream in = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8))) { 96 return builder.parse(in); 97 } 98 } 99 100 public static Document loadDocument(File file, boolean nsAware) 101 throws IOException, SAXException, ParserConfigurationException { 102 DocumentBuilder builder = newDocumentBuilder(nsAware); 103 // Not using the parse(File) method on purpose, so that we can control when 104 // to close it. Somehow parse(File) does not seem to close the file in all cases. 105 try (InputStream in = Files.newInputStream(file.toPath())) { 106 return builder.parse(in); 107 } 108 } 109 110 public static void saveDocument(Document doc, File file) 111 throws IOException, SAXException, ParserConfigurationException, TransformerException { 112 TransformerFactory factory = TransformerFactory.newInstance(); 113 Transformer transformer = factory.newTransformer(); 114 transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); 115 116 byte[] xmlDecl = "<?xml version=\"1.0\" encoding=\"utf-8\"?>".getBytes(StandardCharsets.US_ASCII); 117 byte[] newLine = System.getProperty("line.separator").getBytes(StandardCharsets.US_ASCII); 118 119 try (OutputStream out = Files.newOutputStream(file.toPath())) { 120 out.write(xmlDecl); 121 out.write(newLine); 122 transformer.transform(new DOMSource(doc), new StreamResult(out)); 123 out.write(newLine); 124 } 125 } 126 127 @SuppressWarnings("unchecked") 128 public static <T> T evaluateXPath(Document doc, String expression, Class<T> returnType) 129 throws XPathExpressionException { 130 QName type; 131 if (returnType == Node.class) { 132 type = XPathConstants.NODE; 133 } else if (returnType == NodeList.class) { 134 type = XPathConstants.NODESET; 135 } else if (returnType == String.class) { 136 type = XPathConstants.STRING; 137 } else if (returnType == Double.class) { 138 type = XPathConstants.NUMBER; 139 } else if (returnType == Boolean.class) { 140 type = XPathConstants.BOOLEAN; 141 } else { 142 throw new IllegalArgumentException("Unexpected return type: " + returnType.getName()); 143 } 144 145 XPath xPath = XPathFactory.newInstance().newXPath(); 146 xPath.setNamespaceContext(new NamespaceContext() { 147 @Override 148 public String getNamespaceURI(String prefix) { 149 return doc.lookupNamespaceURI(prefix); 150 } 151 152 @Override 153 public String getPrefix(String namespaceURI) { 154 return doc.lookupPrefix(namespaceURI); 155 } 156 157 @Override 158 public Iterator<String> getPrefixes(String namespaceURI) { 159 String prefix = getPrefix(namespaceURI); 160 return prefix != null 161 ? Collections.singleton(prefix).iterator() 162 : Collections.emptyIterator(); 163 } 164 }); 165 166 return (T) xPath.evaluate(expression, doc, type); 167 } 168 }