package me.pbox.xml; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.w3c.dom.*; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import javax.xml.namespace.QName; import javax.xml.parsers.*; import javax.xml.transform.*; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.xpath.*; import java.io.*; import java.util.Map; import java.util.Set; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * @author Maxim Shipko (sladethe@gmail.com) * Date: 14.09.11 */ @SuppressWarnings("UnusedDeclaration") public final class XmlUtil { private static final Lock factoryLock = new ReentrantLock(); private static final Lock expressionLock = new ReentrantLock(); private XmlUtil() { throw new UnsupportedOperationException(); } /** * Parses XML string and extracts value. * * @param xmlFile XML to be scanned. * @param xPath XPath expression. * @param clazz {@link Boolean}, {@link String}, {@link Integer}, {@link Double}, * {@link org.w3c.dom.NodeList} and {@link org.w3c.dom.Node} classes are supported now. * @param <T> Return type. * @return Return value. * @throws java.io.IOException In case of I/O error. */ public static <T> T extractFromXml(/*@Nonnull*/ final File xmlFile, final String xPath, final Class<T> clazz) throws IOException { return internalExtractFromXml(new FileInputStream(xmlFile), xPath, clazz); } /** * Parses XML string and extracts value. * * @param xmlInputStream XML to be scanned. * @param xPath XPath expression. * @param clazz {@link Boolean}, {@link String}, {@link Integer}, {@link Double}, * {@link org.w3c.dom.NodeList} and {@link org.w3c.dom.Node} classes are supported now. * @param <T> Return type. * @return Return value. * @throws java.io.IOException In case of I/O error. */ public static <T> T extractFromXml(final InputStream xmlInputStream, final String xPath, final Class<T> clazz) throws IOException { return internalExtractFromXml(xmlInputStream, xPath, clazz); } /** * Writes XML document into file. * * @param xmlFile File to write. * @param document XML document. * @throws java.io.IOException In case of I/O error. */ public static void writeXml(/*@Nonnull*/ final File xmlFile, /*@Nonnull*/ final Document document) throws IOException { internalWriteXml(new FileOutputStream(xmlFile), document); } /** * Writes XML document into file. * * @param xmlOutputStream Stream to write. * @param document XML document. * @throws java.io.IOException In case of I/O error. */ public static void writeXml(final OutputStream xmlOutputStream, /*@Nonnull*/ final Document document) throws IOException { internalWriteXml(xmlOutputStream, document); } /** * Changes the value describing by XPath to specific value and updates file. * * @param xmlFile Which will read first and updated later. * @param xPath XPath to find specific Node. * @param value Value to be set for found node. * @throws java.io.IOException In case of I/O error. */ public static void updateXml(/*@Nonnull*/ final File xmlFile, final String xPath, final String value) throws IOException { try { ByteArrayOutputStream xmlOutputStream = new ByteArrayOutputStream(); internalUpdateXml( new ByteArrayInputStream(FileUtils.readFileToByteArray(xmlFile)), xmlOutputStream, xPath, value ); FileUtils.writeByteArrayToFile(xmlFile, xmlOutputStream.toByteArray()); } catch (IOException e) { throw new IOException( "Can't find, read or update file '" + xmlFile.getName() + "' while evaluating XPath '" + xPath + "'.", e ); } } /** * Changes the value describing by XPath to specific value and writes modified XML document into output stream. * * @param xmlInputStream Stream to read. * @param xmlOutputStream Stream to write. * @param xPath XPath to find specific Node. * @param value Value to be set for found node. * @throws java.io.IOException In case of I/O error. */ public static void updateXml( final InputStream xmlInputStream, final OutputStream xmlOutputStream, final String xPath, final String value) throws IOException { internalUpdateXml(xmlInputStream, xmlOutputStream, xPath, value); } /** * Changes the inner text of an XML-element described by XPath to specific value and updates file. * * @param xmlFile Which will read first and updated later. * @param xPath XPath to find specific {@code {@link org.w3c.dom.Element }}. * @param value New text value. * @throws java.io.IOException In case of I/O error. */ public static void updateText(/*@Nonnull*/ final File xmlFile, final String xPath, /*@Nullable*/ final String value) throws IOException { try { ByteArrayOutputStream xmlOutputStream = new ByteArrayOutputStream(); internalUpdateText( new ByteArrayInputStream(FileUtils.readFileToByteArray(xmlFile)), xmlOutputStream, xPath, value ); FileUtils.writeByteArrayToFile(xmlFile, xmlOutputStream.toByteArray()); } catch (IOException e) { throw new IOException( "Can't find, read or update file '" + xmlFile.getName() + "' while evaluating XPath '" + xPath + "'.", e ); } } /** * Changes the inner text of an XML-element described by XPath to specific value * and writes modified XML document into output stream. * * @param xmlInputStream Stream to read. * @param xmlOutputStream Stream to write. * @param xPath XPath to find specific {@code {@link org.w3c.dom.Element }}. * @param value New text value. * @throws java.io.IOException In case of I/O error. */ public static void updateText( final InputStream xmlInputStream, final OutputStream xmlOutputStream, final String xPath, /*@Nullable*/ final String value) throws IOException { internalUpdateText(xmlInputStream, xmlOutputStream, xPath, value); } /** * Ensures that XML-element with {@code newAttributes} does exist and creates it if not. * <p/> * Method uses {@code filterAttributes} to uniquely identify an XML-element. * If such element does exist, all its attributes will be overriden with values of {@code newAttributes}, * else a new element will be created. * * @param xmlFile Which will read first and updated later. * @param parentElementXPath XPath to find element that should contain specified element. * @param elementName Name of the element to create. * @param filterAttributes Collection of attributes which allows to uniquely identify an XML-element. * @param newAttributes Collection of attributes which an XML-element should have * or {@code null} if {@code filterAttributes} should be considered * also as {@code newAttributes}. * @param obsoleteAttributes Collection of attribute names which should be removed from the element * or {@code null} if no such action is required. * @throws java.io.IOException In case of I/O error. */ public static void ensureXmlElementExists( /*@Nonnull*/ final File xmlFile, /*@Nonnull*/ final String parentElementXPath, /*@Nonnull*/ final String elementName, /*@Nonnull*/ final Map<String, String> filterAttributes, /*@Nullable*/ final Map<String, String> newAttributes, /*@Nullable*/ final Set<String> obsoleteAttributes) throws IOException { try { ByteArrayOutputStream xmlOutputStream = new ByteArrayOutputStream(); internalEnsureXmlElementExists( new ByteArrayInputStream(FileUtils.readFileToByteArray(xmlFile)), xmlOutputStream, parentElementXPath, elementName, filterAttributes, newAttributes, obsoleteAttributes ); FileUtils.writeByteArrayToFile(xmlFile, xmlOutputStream.toByteArray()); } catch (IOException e) { throw new IOException( "Can't find, read or update file '" + xmlFile.getName() + "' while evaluating XPath '" + parentElementXPath + "'.", e ); } } /** * Ensures that XML-element with {@code newAttributes} does exist and creates it if not. * <p/> * Method uses {@code filterAttributes} to uniquely identify an XML-element. * If such element does exist, all its attributes will be overriden with values of {@code newAttributes}, * else a new element will be created. * * @param xmlInputStream Stream to read. * @param xmlOutputStream Stream to write. * @param parentElementXPath XPath to find element that should contain specified element. * @param elementName Name of the element to create. * @param filterAttributes Collection of attributes which allows to uniquely identify an XML-element. * @param newAttributes Collection of attributes which an XML-element should have * or {@code null} if {@code filterAttributes} should be considered * also as {@code newAttributes}. * @param obsoleteAttributes Collection of attribute names which should be removed from the element * or {@code null} if no such action is required. * @throws java.io.IOException In case of I/O error. */ public static void ensureXmlElementExists( /*@Nonnull*/ final InputStream xmlInputStream, /*@Nonnull*/ final OutputStream xmlOutputStream, /*@Nonnull*/ final String parentElementXPath, /*@Nonnull*/ final String elementName, /*@Nonnull*/ final Map<String, String> filterAttributes, /*@Nullable*/ final Map<String, String> newAttributes, /*@Nullable*/ final Set<String> obsoleteAttributes) throws IOException { internalEnsureXmlElementExists( xmlInputStream, xmlOutputStream, parentElementXPath, elementName, filterAttributes, newAttributes, obsoleteAttributes ); } /** * Removes all elements Xml by XPath. * * @param xmlFile From which will be removed elements. * @param elementXPath XPath of elements to remove. * @throws java.io.IOException In case of I/O error. */ public static void removeElementsIfExists(/*@Nonnull*/ File xmlFile, /*@Nonnull*/ String elementXPath) throws IOException { try { ByteArrayOutputStream xmlOutputStream = new ByteArrayOutputStream(); removeElementsIfExists(new ByteArrayInputStream(FileUtils.readFileToByteArray(xmlFile)), xmlOutputStream, elementXPath); FileUtils.writeByteArrayToFile(xmlFile, xmlOutputStream.toByteArray()); } catch (IOException e) { throw new IOException( "Can't find, read or update file '" + xmlFile.getName() + "' while evaluating XPath '" + elementXPath + "'.", e ); } } /** * Removes all elements from Xml by XPath. * * @param xmlInputStream Stream to read. * @param xmlOutputStream Stream to write. * @param elementXPath XPath of elements to remove. @throws IOException In case of I/O error. */ public static void removeElementsIfExists( /*@Nonnull*/ InputStream xmlInputStream, /*@Nonnull*/ OutputStream xmlOutputStream, /*@Nonnull*/ String elementXPath) throws IOException { internalRemoveElementsIfExists(xmlInputStream, xmlOutputStream, elementXPath); } /*@Nullable*/ public static String formatEnumValueForXml(/*@Nullable*/ Enum enumValue) { return enumValue == null ? null : enumValue.name().toLowerCase().replace('_', '-'); } /*@Nullable*/ public static <T extends Enum<T>> T extractEnumValueFromXml(/*@Nonnull*/ String enumFormat, Class<T> enumClass) { return StringUtils.isBlank(enumFormat) ? null : Enum.valueOf(enumClass, enumFormat.toUpperCase().replace('-', '_')); } /*@Nonnull*/ @SuppressWarnings("unchecked") private static <T> T internalExtractFromXml(InputStream xmlInputStream, String xPath, Class<T> clazz) throws IOException { byte[] xmlBytes = IOUtils.toByteArray(xmlInputStream); IOUtils.closeQuietly(xmlInputStream); InputStream xmlBytesInputStream = new ByteArrayInputStream(xmlBytes); XPath xp = newXPathFactory().newXPath(); QName type; if (clazz == Boolean.class) { type = XPathConstants.BOOLEAN; } else if (clazz == String.class) { type = XPathConstants.STRING; } else if (clazz == Integer.class || clazz == Double.class) { type = XPathConstants.NUMBER; } else if (clazz == NodeList.class) { type = XPathConstants.NODESET; } else if (clazz == Node.class) { type = XPathConstants.NODE; } else { throw new IllegalArgumentException("Illegal argument 'clazz': '" + clazz + "'."); } try { XPathExpression expression = xp.compile(xPath); Object result = evaluateXPath(xmlBytesInputStream, expression, type); if (XPathConstants.NUMBER.equals(type) && clazz == Integer.class) { result = ((Double) result).intValue(); } return (T) result; } catch (XPathException e) { String xmlString = new String(xmlBytes, "UTF-8"); throw new IOException("Can't get xpath \"" + xPath + "\" from \"" + StringUtils.abbreviate(xmlString, 128) + "\".", e); } finally { IOUtils.closeQuietly(xmlBytesInputStream); } } private static void internalWriteXml(OutputStream xmlOutputStream, /*@Nonnull*/ Document document) throws IOException { formatDocument(document); Source source = new DOMSource(document); try { Transformer transformer = newTransformerFactory().newTransformer(); transformer.setOutputProperty(OutputKeys.STANDALONE, "yes"); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty(OutputKeys.ENCODING, "utf-8"); StreamResult result = new StreamResult(xmlOutputStream); transformer.transform(source, result); } catch (TransformerConfigurationException e) { throw new IOException("Transformer configuration is illegal.", e); } catch (TransformerException e) { throw new IOException("Transformer failed.", e); } finally { IOUtils.closeQuietly(xmlOutputStream); } } private static void internalUpdateXml( InputStream xmlInputStream, OutputStream xmlOutputStream, String xPath, String value) throws IOException { XPath xp = newXPathFactory().newXPath(); try { XPathExpression root = xp.compile("/"); Document document = (Document) evaluateXPath(xmlInputStream, root, XPathConstants.NODE); XPathExpression nodeXPath = xp.compile(xPath); Node node = (Node) evaluateXPath(document, nodeXPath, XPathConstants.NODE); node.setNodeValue(value); internalWriteXml(xmlOutputStream, document); } catch (XPathExpressionException e) { throw new IOException("Illegal XPath.", e); } finally { IOUtils.closeQuietly(xmlInputStream); IOUtils.closeQuietly(xmlOutputStream); } } private static void internalUpdateText( InputStream xmlInputStream, OutputStream xmlOutputStream, String xPath, /*@Nullable*/ String value) throws IOException { XPath xp = newXPathFactory().newXPath(); try { Document document = (Document) evaluateXPath(xmlInputStream, xp.compile("/"), XPathConstants.NODE); NodeList nodes = (NodeList) evaluateXPath(document, xp.compile(xPath), XPathConstants.NODESET); for (int nodeIndex = nodes.getLength() - 1; nodeIndex >= 0; --nodeIndex) { Node node = nodes.item(nodeIndex); if (!(node instanceof Element)) { throw new IOException("Node specified by XPath '" + xPath + "' is not an XML-element."); } Element element = (Element) node; if (!hasOnlyTextChildren(element)) { throw new IOException(String.format( "Element specified by XPath '%s' has at least one child which isn't plain text node.", xPath )); } Node childNode = element.getFirstChild(); if (value == null) { while (childNode != null) { element.removeChild(childNode); childNode = element.getFirstChild(); } } else { if (childNode == null) { element.appendChild(element.getOwnerDocument().createTextNode(value)); } else { ((Text) childNode).replaceWholeText(value); } } } internalWriteXml(xmlOutputStream, document); } catch (XPathExpressionException e) { throw new IOException("Illegal XPath.", e); } finally { IOUtils.closeQuietly(xmlInputStream); IOUtils.closeQuietly(xmlOutputStream); } } /** * Ensures that XML-element with {@code newAttributes} does exist and creates it if not. * <p/> * Method uses {@code filterAttributes} to uniquely identify an XML-element. * If such element does exist, all its attributes will be overriden with values of {@code newAttributes}, * else a new element will be created. * * @param xmlInputStream Stream to read. * @param xmlOutputStream Stream to write. * @param parentElementXPath XPath to find element that should contain specified element. * @param elementName Name of the element to create. * @param filterAttributes Collection of attributes which allows to uniquely identify an XML-element. * @param newAttributes Collection of attributes which an XML-element should have * or {@code null} if {@code filterAttributes} should be considered * also as {@code newAttributes}. * @param obsoleteAttributes Collection of attribute names which should be removed from the element * or {@code null} if no such action is required. * @throws java.io.IOException In case of I/O error. */ @SuppressWarnings({"OverlyLongMethod", "OverlyComplexMethod"}) private static void internalEnsureXmlElementExists( /*@Nonnull*/ InputStream xmlInputStream, /*@Nonnull*/ OutputStream xmlOutputStream, /*@Nonnull*/ String parentElementXPath, /*@Nonnull*/ String elementName, /*@Nonnull*/ Map<String, String> filterAttributes, /*@Nullable*/ Map<String, String> newAttributes, /*@Nullable*/ Set<String> obsoleteAttributes) throws IOException { XPath xp = newXPathFactory().newXPath(); try { // Load DOM-document, compile and execute XPath expression. XPathExpression root = xp.compile("/"); Document document = (Document) evaluateXPath(xmlInputStream, root, XPathConstants.NODE); XPathExpression parentNodeXPath = xp.compile(parentElementXPath); Node parentNode = (Node) evaluateXPath(document, parentNodeXPath, XPathConstants.NODE); NodeList childNodes = parentNode.getChildNodes(); Element element = null; // Search for element with specified characteristics. for (int childIndex = 0, childCount = childNodes.getLength(); childIndex < childCount; ++childIndex) { Node childNode = childNodes.item(childIndex); if (!elementName.equals(childNode.getNodeName())) { continue; } NamedNodeMap actualAttributes = childNode.getAttributes(); if (actualAttributes == null) { continue; } boolean matches = true; for (Map.Entry<String, String> filterAttribute : filterAttributes.entrySet()) { Node actualAttribute = actualAttributes.getNamedItem(filterAttribute.getKey()); if (actualAttribute == null || !filterAttribute.getValue().equals(actualAttribute.getNodeValue())) { matches = false; break; } } if (matches) { element = (Element) childNode; break; } } // Create new element if not found. if (element == null) { element = document.createElement(elementName); parentNode.appendChild(element); } // Create or update attributes. newAttributes = newAttributes == null ? filterAttributes : newAttributes; for (Map.Entry<String, String> newAttribute : newAttributes.entrySet()) { element.setAttribute(newAttribute.getKey(), newAttribute.getValue()); } // Remove obsolete attributes. if (obsoleteAttributes != null) { for (String obsoleteAttribute : obsoleteAttributes) { element.removeAttribute(obsoleteAttribute); } } // Save DOM-document. internalWriteXml(xmlOutputStream, document); } catch (XPathExpressionException e) { throw new IOException("Illegal XPath.", e); } finally { IOUtils.closeQuietly(xmlInputStream); IOUtils.closeQuietly(xmlOutputStream); } } /** * Removes all elements from Xml by XPath. * * @param xmlInputStream Stream to read. * @param xmlOutputStream Stream to write. * @param elementXPath XPath of elements to remove. @throws IOException In case of I/O error. */ private static void internalRemoveElementsIfExists( /*@Nonnull*/ InputStream xmlInputStream, /*@Nonnull*/ OutputStream xmlOutputStream, /*@Nonnull*/ String elementXPath) throws IOException { XPath xp = newXPathFactory().newXPath(); try { XPathExpression root = xp.compile("/"); Document document = (Document) evaluateXPath(xmlInputStream, root, XPathConstants.NODE); XPathExpression parentNodeXPath = xp.compile(elementXPath); NodeList nodeList = (NodeList) evaluateXPath(document, parentNodeXPath, XPathConstants.NODESET); for (int nodeIndex = 0; nodeIndex < nodeList.getLength(); nodeIndex++) { Node node = nodeList.item(nodeIndex); node.getParentNode().removeChild(node); } internalWriteXml(xmlOutputStream, document); } catch (XPathExpressionException e) { throw new IOException("Illegal XPath.", e); } finally { IOUtils.closeQuietly(xmlInputStream); IOUtils.closeQuietly(xmlOutputStream); } } @SuppressWarnings({"HardcodedLineSeparator"}) private static void formatDocument(/*@Nonnull*/ Document document) { formatElement(document, document.getDocumentElement(), 1); formatElementEnd(document, document.getDocumentElement(), "\n"); } @SuppressWarnings({"ChainOfInstanceofChecks", "HardcodedLineSeparator", "OverlyComplexMethod"}) private static void formatElement(/*@Nonnull*/ Document document, /*@Nonnull*/ Element element, int depth) { String formatString = '\n' + StringUtils.repeat(" ", depth); Node node = element.getFirstChild(); while (node != null) { if (node instanceof Element) { formatElement(document, (Element) node, depth + 1); formatElementStart(document, (Element) node, formatString); formatElementEnd(document, (Element) node, formatString); } else if (node instanceof Text) { Text textNode = (Text) node; String text = textNode.getWholeText(); if (hasOnlyTextSiblings(textNode)) { String trimmedText = text.trim(); if (!trimmedText.equals(text)) { textNode.replaceWholeText(trimmedText); } } else { if (!text.trim().isEmpty()) { textNode.replaceWholeText(formatString + trimLeft(text)); } } } node = node.getNextSibling(); } } @SuppressWarnings({"ChainOfInstanceofChecks"}) private static void formatElementStart( /*@Nonnull*/ Document document, /*@Nonnull*/ Element element, /*@Nonnull*/ String formatString) { Node previousSibling = element.getPreviousSibling(); if (previousSibling == null || previousSibling instanceof Element) { element.getParentNode().insertBefore(document.createTextNode(formatString), element); } else if (previousSibling instanceof Text) { Text textNode = (Text) previousSibling; String text = textNode.getWholeText(); if (!formatString.equals(text)) { textNode.replaceWholeText(trimRight(text) + formatString); } } } @SuppressWarnings({"ChainOfInstanceofChecks"}) private static void formatElementEnd( /*@Nonnull*/ Document document, /*@Nonnull*/ Element element, /*@Nonnull*/ String formatString) { Node lastChild = element.getLastChild(); if (lastChild != null) { if (lastChild instanceof Element) { element.appendChild(document.createTextNode(formatString)); } else if (lastChild instanceof Text) { Text textNode = (Text) lastChild; String text = textNode.getWholeText(); if (hasOnlyTextSiblings(textNode)) { String trimmedText = text.trim(); if (!trimmedText.equals(text)) { textNode.replaceWholeText(trimmedText); } } else { if (!formatString.equals(text)) { textNode.replaceWholeText(trimRight(text) + formatString); } } } } } /*@Nullable*/ private static String trimRight(/*@Nullable*/ String s) { if (s == null) { return null; } int lastIndex = s.length() - 1; int index = lastIndex; while (index >= 0 && s.charAt(index) <= ' ') { --index; } return index == lastIndex ? s : s.substring(0, index + 1); } /*@Nullable*/ public static String trimLeft(/*@Nullable*/ String s) { if (s == null) { return null; } int lastIndex = s.length() - 1; int index = 0; while (index <= lastIndex && s.charAt(index) <= ' ') { ++index; } return index == 0 ? s : s.substring(index, lastIndex + 1); } public static boolean hasOnlyTextSiblings(/*@Nonnull*/ Node node) { Node leftSibling = node.getPreviousSibling(); while (leftSibling != null) { if (!(leftSibling instanceof Text)) { return false; } leftSibling = leftSibling.getPreviousSibling(); } Node rightSibling = node.getNextSibling(); while (rightSibling != null) { if (!(rightSibling instanceof Text)) { return false; } rightSibling = rightSibling.getNextSibling(); } return true; } public static boolean hasOnlyTextChildren(/*@Nonnull*/ Node node) { Node childNode = node.getFirstChild(); while (childNode != null) { if (!(childNode instanceof Text)) { return false; } childNode = childNode.getNextSibling(); } return true; } /*@Nonnull*/ public static Document newDomDocument() throws IOException { try { DocumentBuilderFactory factory = newDocumentBuilderFactory(); DocumentBuilder builder = factory.newDocumentBuilder(); return builder.newDocument(); } catch (ParserConfigurationException e) { throw new IOException("Can't create new DOM-document.", e); } } public static XPathFactory newXPathFactory() { factoryLock.lock(); try { return XPathFactory.newInstance(); } finally { factoryLock.unlock(); } } public static TransformerFactory newTransformerFactory() { factoryLock.lock(); try { return TransformerFactory.newInstance(); } finally { factoryLock.unlock(); } } public static DocumentBuilderFactory newDocumentBuilderFactory() { factoryLock.lock(); try { return DocumentBuilderFactory.newInstance(); } finally { factoryLock.unlock(); } } public static Object evaluateXPath(InputStream xmlInputStream, /*@Nonnull*/ XPathExpression xPath, QName returnType) throws XPathExpressionException { expressionLock.lock(); try { return xPath.evaluate(new InputSource(xmlInputStream), returnType); } finally { expressionLock.unlock(); } } public static Object evaluateXPath(Document document, /*@Nonnull*/ XPathExpression xPath, QName returnType) throws XPathExpressionException { expressionLock.lock(); try { return xPath.evaluate(document, returnType); } finally { expressionLock.unlock(); } } public static Object evaluateXPath(Element element, /*@Nonnull*/ XPathExpression xPath, QName returnType) throws XPathExpressionException { expressionLock.lock(); try { return xPath.evaluate(element, returnType); } finally { expressionLock.unlock(); } } public static Object evaluateXPath(InputStream xmlInputStream, /*@Nonnull*/ XPathExpression xPath) throws XPathExpressionException { expressionLock.lock(); try { return xPath.evaluate(new InputSource(xmlInputStream)); } finally { expressionLock.unlock(); } } public static Object evaluateXPath(Document document, /*@Nonnull*/ XPathExpression xPath) throws XPathExpressionException { expressionLock.lock(); try { return xPath.evaluate(document); } finally { expressionLock.unlock(); } } public static Object evaluateXPath(Element element, /*@Nonnull*/ XPathExpression xPath) throws XPathExpressionException { expressionLock.lock(); try { return xPath.evaluate(element); } finally { expressionLock.unlock(); } } public static void traverse(File xmlFile, DefaultHandler handler) { SAXParserFactory spf = SAXParserFactory.newInstance(); spf.setNamespaceAware(true); try { SAXParser saxParser = spf.newSAXParser(); saxParser.parse(xmlFile, handler); } catch (ParserConfigurationException e) { throw new RuntimeException("Can't configure XML-SAX parser.", e); } catch (SAXException e) { throw new RuntimeException("Can't run XML-SAX parser.", e); } catch (IOException e) { throw new RuntimeException("Invalid XML file '" + xmlFile + "'.", e); } } public static void traverse(String xml, DefaultHandler handler) { SAXParserFactory spf = SAXParserFactory.newInstance(); spf.setNamespaceAware(true); try { SAXParser saxParser = spf.newSAXParser(); saxParser.parse(new InputSource(new StringReader(xml)), handler); } catch (ParserConfigurationException e) { throw new RuntimeException("Can't configure XML-SAX parser.", e); } catch (SAXException e) { throw new RuntimeException("Can't run XML-SAX parser.", e); } catch (IOException e) { throw new RuntimeException("Invalid XML '" + xml + "'.", e); } } }