2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6 * Copyright (C) 2017 Huawei Technologies Co., Ltd. All rights reserved.
7 * ================================================================================
8 * Modifications Copyright (c) 2019 Samsung
9 * ================================================================================
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * Unless required by applicable law or agreed to in writing, software
17 * distributed under the License is distributed on an "AS IS" BASIS,
18 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 * See the License for the specific language governing permissions and
20 * limitations under the License.
21 * ============LICENSE_END=========================================================
24 package org.onap.so.bpmn.core.xml;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.io.InputStreamReader;
29 import java.io.Reader;
30 import java.io.StringReader;
31 import java.io.StringWriter;
32 import java.util.HashMap;
34 import java.util.Optional;
35 import javax.xml.XMLConstants;
36 import javax.xml.parsers.DocumentBuilder;
37 import javax.xml.parsers.DocumentBuilderFactory;
38 import javax.xml.parsers.ParserConfigurationException;
39 import javax.xml.transform.OutputKeys;
40 import javax.xml.transform.Source;
41 import javax.xml.transform.Transformer;
42 import javax.xml.transform.TransformerException;
43 import javax.xml.transform.TransformerFactory;
44 import javax.xml.transform.dom.DOMSource;
45 import javax.xml.transform.stream.StreamResult;
46 import javax.xml.transform.stream.StreamSource;
47 import javax.xml.xpath.XPath;
48 import javax.xml.xpath.XPathConstants;
49 import javax.xml.xpath.XPathExpressionException;
50 import javax.xml.xpath.XPathFactory;
51 import org.apache.commons.lang3.StringEscapeUtils;
52 import org.apache.commons.lang3.StringUtils;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55 import org.w3c.dom.Document;
56 import org.w3c.dom.Node;
57 import org.w3c.dom.NodeList;
58 import org.xml.sax.InputSource;
59 import org.xml.sax.SAXException;
62 * XML transformation methods and other useful functions.
64 public final class XmlTool {
66 private static final Map<String, Integer> ENTITIES = new HashMap<>();
67 private static final Logger logger = LoggerFactory.getLogger(XmlTool.class);
69 ENTITIES.put("amp", 38);
70 ENTITIES.put("quot", 34);
71 ENTITIES.put("lt", 60);
72 ENTITIES.put("gt", 62);
76 * Instantiation is not allowed.
81 * Normalizes and formats XML. This method consolidates and moves all namespace declarations to the root element.
82 * The result will not have an XML prolog or a trailing newline.
84 * @param xml the XML to normalize
86 * @throws TransformerException
87 * @throws ParserConfigurationException
88 * @throws SAXException
89 * @throws XPathExpressionException
91 public static String normalize(final Object xml) throws IOException, TransformerException,
92 ParserConfigurationException, SAXException, XPathExpressionException {
98 final Source xsltSource = new StreamSource(new StringReader(readResourceFile("normalize-namespaces.xsl")));
100 final DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
101 dbFactory.setNamespaceAware(true);
102 dbFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
103 dbFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
104 final DocumentBuilder db = dbFactory.newDocumentBuilder();
105 final InputSource source = new InputSource(new StringReader(String.valueOf(xml)));
106 final Document doc = db.parse(source);
108 // Start of code to remove whitespace outside of tags
109 final XPath xPath = XPathFactory.newInstance().newXPath();
110 final NodeList nodeList =
111 (NodeList) xPath.evaluate("//text()[normalize-space()='']", doc, XPathConstants.NODESET);
113 for (int i = 0; i < nodeList.getLength(); ++i) {
114 final Node node = nodeList.item(i);
115 node.getParentNode().removeChild(node);
117 // End of code to remove whitespace outside of tags
119 // the factory pattern supports different XSLT processors
120 final TransformerFactory transformerFactory = TransformerFactory.newInstance();
121 transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, StringUtils.EMPTY);
122 transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, StringUtils.EMPTY);
123 transformerFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
124 final Transformer transformer = transformerFactory.newTransformer(xsltSource);
126 transformer.setOutputProperty(OutputKeys.INDENT, "yes");
127 transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
128 transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
129 transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
131 final StringWriter writer = new StringWriter();
132 transformer.transform(new DOMSource(doc), new StreamResult(writer));
133 return writer.toString().trim();
137 * Encodes a value so it can be used inside an XML text element.
139 * @param value the string to encode
140 * @return the encoded string
142 public static String encode(Object value) {
146 return StringEscapeUtils.escapeXml11(value.toString());
150 * Removes the preamble, if present, from an XML document.
152 * @param xml the XML document
153 * @return a possibly modified document
155 public static String removePreamble(Object xml) {
160 return String.valueOf(xml).replaceAll("(<\\?[^<]*\\?>\\s*[\\r\\n]*)?", "");
164 * Removes namespaces and namespace declarations from an XML document.
166 * @param xml the XML document
167 * @return a possibly modified document
169 public static String removeNamespaces(Object xml) {
171 logger.debug("removeNamespaces input object is null , returning null");
175 String text = String.valueOf(xml);
177 // remove xmlns declaration
178 text = text.replaceAll("xmlns.*?(\"|\').*?(\"|\')", "");
179 // remove opening tag prefix
180 text = text.replaceAll("(<)(\\w+:)(.*?>)", "$1$3");
181 // remove closing tags prefix
182 text = text.replaceAll("(</)(\\w+:)(.*?>)", "$1$3");
183 // remove extra spaces left when xmlns declarations are removed
184 text = text.replaceAll("\\s+>", ">");
191 * Reads the specified resource file and return the contents as a string.
193 * @param file Name of the resource file
194 * @return the contents of the resource file as a String
195 * @throws IOException if there is a problem reading the file
197 private static String readResourceFile(String file) throws IOException {
199 try (InputStream stream = XmlTool.class.getClassLoader().getResourceAsStream(file);
200 Reader reader = new InputStreamReader(stream, "UTF-8")) {
202 StringBuilder out = new StringBuilder();
203 char[] buf = new char[1024];
206 while ((n = reader.read(buf)) >= 0) {
207 out.append(buf, 0, n);
209 return out.toString();
210 } catch (Exception e) {
211 logger.debug("Exception at readResourceFile stream: " + e);
217 * Parses the XML document String for the first occurrence of the specified element tag. If found, the value
218 * associated with that element tag is replaced with the new value and a String containing the modified XML document
219 * is returned. If the XML passed is null or the element tag is not found in the document, null will be returned.
221 * @param xml String containing the original XML document.
222 * @param elementTag String containing the tag of the element to be modified.
223 * @param newValue String containing the new value to be used to modify the corresponding element.
224 * @return the contents of the modified XML document as a String or null/empty if the modification failed.
225 * @throws IOException, TransformerException, ParserConfigurationException, SAXException
227 public static Optional<String> modifyElement(String xml, String elementTag, String newValue)
228 throws IOException, TransformerException, ParserConfigurationException, SAXException {
230 if (xml == null || xml.isEmpty()) {
231 // no XML content to be modified, return empty
232 return Optional.empty();
235 DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
236 dbFactory.setNamespaceAware(true);
237 dbFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
238 dbFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
239 DocumentBuilder db = dbFactory.newDocumentBuilder();
240 InputSource source = new InputSource(new StringReader(xml));
241 Document doc = db.parse(source);
243 Node modNode = doc.getElementsByTagName(elementTag).item(0);
244 if (modNode == null) {
245 // did not find the specified element to be modified, return empty
246 // System.out.println("Did not find element tag " + elementTag + " in XML");
247 return Optional.empty();
249 modNode.setTextContent(newValue);
252 TransformerFactory transformerFactory = TransformerFactory.newInstance();
253 Transformer transformer = transformerFactory.newTransformer();
254 StringWriter writer = new StringWriter();
255 transformer.transform(new DOMSource(doc), new StreamResult(writer));
256 // return the modified String representation of the XML
257 return Optional.of(writer.toString().trim());