/** * Copyright (c) Codice Foundation * <p> * This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser * General Public License as published by the Free Software Foundation, either version 3 of the * License, or any later version. * <p> * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. A copy of the GNU Lesser General Public License * is distributed along with this program and can be found at * <http://www.gnu.org/licenses/lgpl.html>. */ package ddf.util; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; import javax.xml.namespace.NamespaceContext; import javax.xml.namespace.QName; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.TransformerFactoryConfigurationError; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathExpressionException; import org.apache.xml.serializer.OutputPropertiesFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.ls.DOMImplementationLS; import org.w3c.dom.ls.LSException; import org.w3c.dom.ls.LSOutput; import org.w3c.dom.ls.LSSerializer; import org.xml.sax.InputSource; import org.xml.sax.SAXException; /** * @author Ashraf Barakat, rodgersh * */ public class XPathHelper { private static final String UTF_8 = "UTF-8"; /** The Logger for this class. */ private static final Logger LOGGER = LoggerFactory.getLogger(XPathHelper.class); private final DocumentBuilderFactory dbf; private final TransformerFactory tf; /** The XML document being worked on by this XPathHelper utility class. */ private Document document; public XPathHelper() { dbf = DocumentBuilderFactory.newInstance(org.apache.xerces.jaxp.DocumentBuilderFactoryImpl.class.getName(), this.getClass() .getClassLoader()); dbf.setNamespaceAware(true); try { dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false); dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); } catch (ParserConfigurationException e) { LOGGER.debug("Unable to set features on document builder.", e); } tf = TransformerFactory.newInstance(org.apache.xalan.processor.TransformerFactoryImpl.class.getName(), this.getClass() .getClassLoader()); } /** * @param document * - To parse */ public XPathHelper(Document document) { // Hugh Rodgers - 6/7/2011 // This code seemed to work except it was not reliable for namespace aware // xpath expressions. The String constructor always worked, hence the new code // to always use the String constructor. // this.document = (Document) document.cloneNode(true); // this.document.getDocumentElement().normalize(); this(xmlToString(document)); } public XPathHelper(Document document, boolean cloneAndNormalize) { this(); if (cloneAndNormalize) { this.document = (Document) document.cloneNode(true); this.document.getDocumentElement() .normalize(); } else { this.document = document; } } /** * @param xmlText */ public XPathHelper(String xmlText) { this(); InputSource is = new InputSource(new StringReader(xmlText)); dbf.setNamespaceAware(true); DocumentBuilder builder; org.w3c.dom.Document doc = null; try { Thread thread = Thread.currentThread(); ClassLoader loader = thread.getContextClassLoader(); thread.setContextClassLoader(XPathHelper.class.getClassLoader()); try { builder = dbf.newDocumentBuilder(); builder.setErrorHandler(null); doc = builder.parse(is); doc.getDocumentElement() .normalize(); this.document = doc; } finally { thread.setContextClassLoader(loader); } } catch (ParserConfigurationException | SAXException | IOException e) { LOGGER.debug(e.getMessage(), e); } } /** * Prints a given node as a String. This is a convenience method that uses the default character * encoding. * * @param n * - the node to print as a String * @return the Node as a String, null if an exception is thrown or null is passed in. */ public static String print(Node n) { return print(n, UTF_8); } /** * Prints a given node as a String * * @param n * - the node to print as a String * @param encoding * - the character encoding to use for the returned String * @return the Node as a String, null if an exception is thrown or null is passed in. */ public static String print(Node n, String encoding) { if (n == null) { return null; } try { Document document = null; if (n instanceof Document) { document = (Document) n; } else { document = n.getOwnerDocument(); } StringWriter stringOut = new StringWriter(); DOMImplementationLS domImpl = (DOMImplementationLS) document.getImplementation(); LSSerializer serializer = domImpl.createLSSerializer(); LSOutput lsOut = domImpl.createLSOutput(); lsOut.setEncoding(encoding); lsOut.setCharacterStream(stringOut); serializer.write(n, lsOut); return stringOut.toString(); } catch (DOMException | LSException e) { LOGGER.debug(e.getMessage(), e); } return null; } /** * Convert an XML Node to a string representation. * * @param node * the XML node to be converted * * @return the string representation of the XML node */ public static String xmlToString(Node node) { return print(node); } /** * Convenience method for evaluating xpath expressions that uses as default for its * {@link NamespaceContext}, the {@link NamespaceResolver} class * * @param xpathExpressionKey * @return a String of the matched xpath evaluation * @throws XPathExpressionException */ public String evaluate(String xpathExpressionKey) throws XPathExpressionException { return (String) this.evaluate(xpathExpressionKey, XPathConstants.STRING, XPathCache.getNamespaceResolver()); } /** * @param xpathExpressionKey * @param nsContext * @return * @throws XPathExpressionException */ public String evaluate(String xpathExpressionKey, NamespaceContext nsContext) throws XPathExpressionException { return (String) this.evaluate(xpathExpressionKey, XPathConstants.STRING, nsContext); } /** * Convenience method for evaluating xpaths that uses as default for its * {@link NamespaceContext}, the {@link NamespaceResolver} class. Allows you to also change the * how the type is returned. * * @param xpathExpressionKey * @param returnType * @throws XPathExpressionException */ public Object evaluate(String xpathExpressionKey, QName returnType) throws XPathExpressionException { return this.evaluate(xpathExpressionKey, returnType, XPathCache.getNamespaceResolver()); } /** * @param xpathExpressionKey * @param returnType * @param nsContext * @return * @throws XPathExpressionException */ public synchronized Object evaluate(String xpathExpressionKey, QName returnType, NamespaceContext nsContext) throws XPathExpressionException { XPathCache.getXPath() .setNamespaceContext(nsContext); XPathExpression compiledExpression = XPathCache.getCompiledExpression(xpathExpressionKey); Thread thread = Thread.currentThread(); ClassLoader loader = thread.getContextClassLoader(); thread.setContextClassLoader(this.getClass() .getClassLoader()); DocumentBuilder documentBuilder; byte[] array; try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) { documentBuilder = dbf.newDocumentBuilder(); TransformerFactory transformerFactory = TransformerFactory.newInstance(); Transformer transformer = transformerFactory.newTransformer(); DOMSource source = new DOMSource(document); StreamResult result = new StreamResult(bos); transformer.transform(source, result); array = bos.toByteArray(); } catch (IOException | ParserConfigurationException | TransformerException e) { throw new XPathExpressionException(e); } try (ByteArrayInputStream bis = new ByteArrayInputStream(array)) { return compiledExpression.evaluate(documentBuilder.parse(bis), returnType); } catch (IOException | SAXException e) { throw new XPathExpressionException(e); } finally { thread.setContextClassLoader(loader); } } /** * @param xmlDeclaration * @param indent * @return */ public String print(String xmlDeclaration, String indent) { Transformer serializer; try { serializer = tf.newTransformer(); serializer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, xmlDeclaration); serializer.setOutputProperty(OutputKeys.INDENT, indent); serializer.setOutputProperty(OutputPropertiesFactory.S_KEY_CONTENT_HANDLER, org.apache.xml.serializer.ToXMLStream.class.getName()); StringWriter writer = new StringWriter(); serializer.transform(new DOMSource(document), new StreamResult(writer)); return writer.toString(); } catch (TransformerFactoryConfigurationError | TransformerException e) { LOGGER.debug(e.getMessage(), e); } return null; } /** * Retrieve the XML document being worked on by this XPathHelper utility class. * * @return the XML document */ public Document getDocument() { return document; } }