/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.core.utils.common.xml.impl; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.StringReader; import java.io.StringWriter; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.Text; import org.xml.sax.ErrorHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import de.rcenvironment.core.utils.common.xml.XMLException; import de.rcenvironment.core.utils.common.xml.XMLNamespaceContext; import de.rcenvironment.core.utils.common.xml.XSLTErrorHandler; import de.rcenvironment.core.utils.common.xml.api.XMLSupportService; /** * Default Implementation of the XML Support. * * @author Brigitte Boden * @author Markus Litz, Markus Kunde, Arne Bachmann (some code adapted from old class XMLHelper) */ public class XMLSupportServiceImpl implements XMLSupportService { private static final String NO_NODE_FOUND_FOR_THE_XPATH_EXPRESSION = "No node found for the xpath expression "; private static final String ERROR_WHILE_PARSING_XML_FILE = "Error while parsing XML file: "; private static final String ERROR_WHILE_PARSING_XML_STRING = "Error while reading XML string: "; /** * XPath delimiter string (slash instead of backslash). */ private static final String XPATH_DELIMITER = "/"; /** * Quote character string used in XPath attributes. */ private static final String QUOTE_CHAR = "\""; /** * XML-Namespace delimiter character. */ private static final String NS_DELIMITER = ":"; /** * Error handler for XML Support. * * @author Brigitte Boden */ class XMLSupportErrorHandler implements ErrorHandler { private static final String ERROR_OCCURED_IN_DOCUMENT_BUILDER = "Error occured in document builder: "; /** * {@inheritDoc} * * @see org.xml.sax.ErrorHandler#error(org.xml.sax.SAXParseException) */ @Override public void error(SAXParseException arg0) throws SAXException { throw arg0; } /** * {@inheritDoc} * * @see org.xml.sax.ErrorHandler#fatalError(org.xml.sax.SAXParseException) */ @Override public void fatalError(SAXParseException arg0) throws SAXException { throw arg0; } /** * {@inheritDoc} * * @see org.xml.sax.ErrorHandler#warning(org.xml.sax.SAXParseException) */ @Override public void warning(SAXParseException arg0) throws SAXException { log.warn(ERROR_OCCURED_IN_DOCUMENT_BUILDER + arg0.toString()); } } private DocumentBuilderFactory dbf; private TransformerFactory transformerFactory; /** * Factory for xpath objects. */ private XPathFactory xpathFactory; private Log log; public XMLSupportServiceImpl() { log = LogFactory.getLog(getClass()); dbf = DocumentBuilderFactory.newInstance(); dbf.setIgnoringElementContentWhitespace(true); dbf.setNamespaceAware(true); dbf.setValidating(false); transformerFactory = TransformerFactory.newInstance(); transformerFactory.setErrorListener(new XSLTErrorHandler()); xpathFactory = XPathFactory.newInstance(); } /** * We have to initialize a new formatter because it is not thread safe. * @throws XMLException if transformer cannot be created. * */ private Transformer getNewFormatter() throws XMLException { try { StreamSource source = new StreamSource(getClass().getClassLoader().getResourceAsStream("XMLFormatter.xslt")); Transformer formatter = transformerFactory.newTransformer(source); formatter.setErrorListener(new XSLTErrorHandler()); return formatter; } catch (TransformerConfigurationException e1) { throw new XMLException("Transformer for formatting could not be created: " + e1.toString()); } } @Override public Document readXMLFromFile(File file) throws XMLException { Document doc; try { DocumentBuilder db = getNewDocBuilder(); doc = db.parse(file); } catch (SAXException | IOException e) { throw new XMLException(ERROR_WHILE_PARSING_XML_FILE + file.getAbsolutePath() + " " + e.toString()); } catch (NullPointerException | IllegalArgumentException e) { throw new XMLException(ERROR_WHILE_PARSING_XML_FILE + e.toString()); } return doc; } @Override public Document readXMLFromStream(InputStream stream) throws XMLException { Document doc; try { DocumentBuilder db = getNewDocBuilder(); doc = db.parse(stream); } catch (SAXException | IOException | NullPointerException e) { throw new XMLException(ERROR_WHILE_PARSING_XML_FILE + e.toString()); } return doc; } @Override public Document readXMLFromString(String aString) throws XMLException { Document doc; try { DocumentBuilder db = getNewDocBuilder(); doc = db.parse(new InputSource(new StringReader(aString))); } catch (SAXException | IOException e) { throw new XMLException(ERROR_WHILE_PARSING_XML_STRING + aString + " " + e.toString()); } catch (NullPointerException e) { throw new XMLException(ERROR_WHILE_PARSING_XML_STRING + e.toString()); } return doc; } @Override public Node createElementTree(Document doc, String xPathStr) throws XMLException { Node parentNode = doc.getDocumentElement(); try { final XPath xpath = xpathFactory.newXPath(); xpath.setNamespaceContext(new XMLNamespaceContext(doc)); final StringBuilder currPath = new StringBuilder(""); final String[] elements = xPathStr.split(XPATH_DELIMITER); for (String element : elements) { if (element.length() == 0) { continue; } // Test if node exists for the current xpath currPath.append(XPATH_DELIMITER).append(element); final Node tempNode = (Node) xpath.evaluate(currPath.toString(), doc, XPathConstants.NODE); if (tempNode != null) { parentNode = tempNode; continue; } // Node doesn't exists, create it final Element elementNode = createElement(doc, element); if (parentNode == null) { doc.appendChild(elementNode); } else { parentNode.appendChild(elementNode); } parentNode = elementNode; } } catch (final XPathExpressionException e) { throw new XMLException("Error while building element tree: " + e.toString()); } return parentNode; } @Override public Element createElement(Document doc, String elementString) throws XMLException { if (doc == null) { throw new XMLException("Cannot create element, document is null"); } final String[] elementParts = elementString.split("\\[|\\]"); if (elementParts.length == 0) { return null; } final String elementName = elementParts[0].trim(); if (elementName.length() == 0) { return null; } // Test if element name contains a namespace prefix and create element Element elementNode; final String[] nameParts = elementName.split(NS_DELIMITER); if (nameParts.length == 1) { elementNode = doc.createElement(elementName); } else { elementNode = doc.createElementNS(doc.lookupNamespaceURI(nameParts[0]), elementName); } // Loop over element predicates of the form // [@attrname1="value1"] or // [@ns:attrname2="value2"] or // [ns:subnodename] or // [subnodename="value3"] etc. for (int j = 1; j < elementParts.length; j++) { final String predicate = elementParts[j].trim(); if (predicate.length() == 0) { continue; } if (Character.isDigit(predicate.charAt(0))) { continue; } if (predicate.startsWith("@")) { // It's an attribute of the current element // Remove '@' at the beginning String attrStr = predicate.substring(1); String[] attribute = attrStr.split("="); String attrName = attribute[0]; String attrValue = ""; if (attribute.length == 2) { attrValue = attribute[1]; } if (attrValue.startsWith(QUOTE_CHAR)) { attrValue = attrValue.substring(1); } if (attrValue.endsWith(QUOTE_CHAR)) { attrValue = attrValue.substring(0, attrValue.length() - 1); } // Test if attribute name contains a namespace prefix String[] attrNameParts = attrName.split(NS_DELIMITER); if (attrNameParts.length == 1) { elementNode.setAttribute(attrName, attrValue); } else { elementNode.setAttributeNS(doc.lookupNamespaceURI(attrNameParts[0]), attrName, attrValue); } } else { // It's an subelement of the current element Element subNode; String[] subNodeParts = predicate.split("="); String subNodeName = subNodeParts[0]; String subNodeValue = ""; if (subNodeParts.length == 2) { subNodeValue = subNodeParts[1]; } if (subNodeValue.startsWith(QUOTE_CHAR)) { subNodeValue = subNodeValue.substring(1); } if (subNodeValue.endsWith(QUOTE_CHAR)) { subNodeValue = subNodeValue.substring(0, subNodeValue.length() - 1); } // Test if element name contains a namespace prefix String[] subNodeNameParts = predicate.split(NS_DELIMITER); if (subNodeNameParts.length == 1) { subNode = doc.createElement(subNodeName); } else { subNode = doc.createElementNS(doc.lookupNamespaceURI(subNodeNameParts[0]), subNodeName); } if (subNodeValue.length() > 0) { // The subelement contains a text value Text textNode = doc.createTextNode(subNodeValue); subNode.appendChild(textNode); } // Finally append the subnode to the current element elementNode.appendChild(subNode); } } return elementNode; } @Override public Document createDocument() throws XMLException { DocumentBuilder db = getNewDocBuilder(); return db.newDocument(); } @Override public void deleteElement(Document doc, String xPathStr) throws XMLException { try { XPath xpath = xpathFactory.newXPath(); xpath.setNamespaceContext(new XMLNamespaceContext(doc)); final NodeList nodes = (NodeList) xpath.evaluate(xPathStr, doc, XPathConstants.NODESET); for (int i = 0; i < nodes.getLength(); i++) { final Node node = nodes.item(i); final Node parentNode = node.getParentNode(); parentNode.removeChild(node); } } catch (final XPathExpressionException e) { throw new XMLException("Error while deleting element: " + e.toString()); } } @Override public void replaceNodeText(Document doc, String xPathStr, String newValue) throws XMLException { try { XPath xpath = xpathFactory.newXPath(); xpath.setNamespaceContext(new XMLNamespaceContext(doc)); final NodeList nodes = (NodeList) xpath.evaluate(xPathStr, doc, XPathConstants.NODESET); if (nodes.getLength() == 0) { throw new XMLException(NO_NODE_FOUND_FOR_THE_XPATH_EXPRESSION + xPathStr); } else { for (int i = 0; i < nodes.getLength(); i++) { final Node node = nodes.item(i); node.setTextContent(newValue); } } } catch (XPathExpressionException e) { throw new XMLException("Error while replacing node text: " + e.toString()); } catch (DOMException e) { throw new XMLException("Error while replacing node text: " + e.toString()); } } @Override public void writeXMLtoFile(Document doc, File file) throws XMLException { // Use the absolute path instead of the file here to circumvent a bug in the Transformer. final StreamResult result = new StreamResult(file.getAbsolutePath()); final DOMSource source = new DOMSource(doc); try { getNewFormatter().transform(source, result); } catch (TransformerException e) { throw new XMLException("Error while formatting XML file " + file.getAbsolutePath() + ": " + e.toString()); } } @Override public String writeXMLToString(Document doc) throws XMLException { if (doc == null) { throw new XMLException("Error while formatting XML file, input Document was null."); } final StringWriter writer = new StringWriter(); final StreamResult result = new StreamResult(writer); final DOMSource source = new DOMSource(doc); try { getNewFormatter().transform(source, result); } catch (TransformerException e) { throw new XMLException("Error while formatting XML file: " + e.toString()); } return writer.toString(); } @Override public String writeXMLToString(Element sourceElement) throws XMLException { Document tempDoc = createDocument(); Element importElement = (Element) tempDoc.importNode(sourceElement, true); tempDoc.appendChild(importElement); return writeXMLToString(tempDoc); } @Override public String getElementText(Document doc, String xpathStatement) throws XMLException { String errorMessage = "Failed to find element for given XPath: "; XPath xpath = xpathFactory.newXPath(); String result = null; try { Node node = (Node) xpath.evaluate(xpathStatement, doc, XPathConstants.NODE); if (node == null) { throw new XMLException(errorMessage + xpathStatement); } else { if (node.getFirstChild() == null) { throw new XMLException(errorMessage + xpathStatement); } else { result = node.getFirstChild().getNodeValue(); } } } catch (XPathExpressionException e) { throw new XMLException(errorMessage + xpathStatement, e); } return result; } // We have to initialize a new document builder every time because the DocumentBuilder is not thread safe. private DocumentBuilder getNewDocBuilder() throws XMLException { DocumentBuilder db = null; try { db = dbf.newDocumentBuilder(); db.setErrorHandler(new XMLSupportErrorHandler()); return db; } catch (ParserConfigurationException e) { throw new XMLException("Document builder could not be created: " + e.toString()); } } }