/ brut.j.xml / src / main / java / brut / xml / XmlUtils.java
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  }