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;
36 import javax.xml.XMLConstants;
37 import javax.xml.parsers.DocumentBuilder;
38 import javax.xml.parsers.DocumentBuilderFactory;
39 import javax.xml.parsers.ParserConfigurationException;
40 import javax.xml.transform.OutputKeys;
41 import javax.xml.transform.Source;
42 import javax.xml.transform.Transformer;
43 import javax.xml.transform.TransformerException;
44 import javax.xml.transform.TransformerFactory;
45 import javax.xml.transform.dom.DOMSource;
46 import javax.xml.transform.stream.StreamResult;
47 import javax.xml.transform.stream.StreamSource;
48 import javax.xml.xpath.XPath;
49 import javax.xml.xpath.XPathConstants;
50 import javax.xml.xpath.XPathExpressionException;
51 import javax.xml.xpath.XPathFactory;
53 import org.apache.commons.lang3.StringEscapeUtils;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
56 import org.w3c.dom.Document;
57 import org.w3c.dom.Node;
58 import org.w3c.dom.NodeList;
59 import org.xml.sax.InputSource;
60 import org.xml.sax.SAXException;
63 * XML transformation methods and other useful functions.
65 public final class XmlTool {
67 private static final Map<String, Integer> ENTITIES = new HashMap<>();
68 private static final Logger logger = LoggerFactory.getLogger(XmlTool.class);
70 ENTITIES.put("amp", 38);
71 ENTITIES.put("quot", 34);
72 ENTITIES.put("lt", 60);
73 ENTITIES.put("gt", 62);
77 * Instantiation is not allowed.
83 * Normalizes and formats XML. This method consolidates and moves all namespace
84 * declarations to the root element. The result will not have an XML prolog or
86 * @param xml the XML to normalize
88 * @throws TransformerException
89 * @throws ParserConfigurationException
90 * @throws SAXException
91 * @throws XPathExpressionException
93 public static String normalize(Object xml) throws IOException, TransformerException,
94 ParserConfigurationException, SAXException, XPathExpressionException {
100 Source xsltSource = new StreamSource(new StringReader(
101 readResourceFile("normalize-namespaces.xsl")));
103 DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
104 dbFactory.setNamespaceAware(true);
105 dbFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
106 dbFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
107 DocumentBuilder db = dbFactory.newDocumentBuilder();
108 InputSource source = new InputSource(new StringReader(String.valueOf(xml)));
109 Document doc = db.parse(source);
111 // Start of code to remove whitespace outside of tags
112 XPath xPath = XPathFactory.newInstance().newXPath();
113 NodeList nodeList = (NodeList) xPath.evaluate(
114 "//text()[normalize-space()='']", doc, XPathConstants.NODESET);
116 for (int i = 0; i < nodeList.getLength(); ++i) {
117 Node node = nodeList.item(i);
118 node.getParentNode().removeChild(node);
120 // End of code to remove whitespace outside of tags
122 // the factory pattern supports different XSLT processors
123 TransformerFactory transformerFactory = TransformerFactory.newInstance();
124 transformerFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
125 Transformer transformer = transformerFactory.newTransformer(xsltSource);
127 transformer.setOutputProperty(OutputKeys.INDENT, "yes");
128 transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
129 transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
130 transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
132 StringWriter writer = new StringWriter();
133 transformer.transform(new DOMSource(doc), new StreamResult(writer));
134 return writer.toString().trim();
138 * 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.
151 * @param xml the XML document
152 * @return a possibly modified document
154 public static String removePreamble(Object xml) {
159 return String.valueOf(xml).replaceAll("(<\\?[^<]*\\?>\\s*[\\r\\n]*)?", "");
163 * Removes namespaces and namespace declarations from an XML document.
164 * @param xml the XML document
165 * @return a possibly modified document
167 public static String removeNamespaces(Object xml) {
169 logger.debug("removeNamespaces input object is null , returning null");
173 String text = String.valueOf(xml);
175 // remove xmlns declaration
176 text = text.replaceAll("xmlns.*?(\"|\').*?(\"|\')", "");
177 // remove opening tag prefix
178 text = text.replaceAll("(<)(\\w+:)(.*?>)", "$1$3");
179 // remove closing tags prefix
180 text = text.replaceAll("(</)(\\w+:)(.*?>)", "$1$3");
181 // remove extra spaces left when xmlns declarations are removed
182 text = text.replaceAll("\\s+>", ">");
189 * Reads the specified resource file and return the contents as a string.
190 * @param file Name of the resource file
191 * @return the contents of the resource file as a String
192 * @throws IOException if there is a problem reading the file
194 private static String readResourceFile(String file) throws IOException {
196 try (InputStream stream = XmlTool.class.getClassLoader().getResourceAsStream(file);
197 Reader reader = new InputStreamReader(stream, "UTF-8")) {
199 StringBuilder out = new StringBuilder();
200 char[] buf = new char[1024];
203 while ((n = reader.read(buf)) >= 0) {
204 out.append(buf, 0, n);
206 return out.toString();
207 } catch (Exception e) {
208 logger.debug("Exception at readResourceFile stream: " + e);
214 * Parses the XML document String for the first occurrence of the specified element tag.
215 * If found, the value associated with that element tag is replaced with the new value
216 * and a String containing the modified XML document is returned. If the XML passed is
217 * null or the element tag is not found in the document, null will be returned.
218 * @param xml String containing the original XML document.
219 * @param elementTag String containing the tag of the element to be modified.
220 * @param newValue String containing the new value to be used to modify the corresponding element.
221 * @return the contents of the modified XML document as a String or null/empty if the modification failed.
222 * @throws IOException, TransformerException, ParserConfigurationException, SAXException
224 public static Optional<String> modifyElement(String xml, String elementTag, String newValue) throws IOException, TransformerException,
225 ParserConfigurationException, SAXException {
227 if (xml == null || xml.isEmpty()) {
228 // no XML content to be modified, return empty
229 return Optional.empty();
232 DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
233 dbFactory.setNamespaceAware(true);
234 dbFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
235 dbFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
236 DocumentBuilder db = dbFactory.newDocumentBuilder();
237 InputSource source = new InputSource(new StringReader(xml));
238 Document doc = db.parse(source);
240 Node modNode = doc.getElementsByTagName(elementTag).item(0);
241 if (modNode == null) {
242 // did not find the specified element to be modified, return empty
243 //System.out.println("Did not find element tag " + elementTag + " in XML");
244 return Optional.empty();
246 modNode.setTextContent(newValue);
249 TransformerFactory transformerFactory = TransformerFactory.newInstance();
250 Transformer transformer = transformerFactory.newTransformer();
251 StringWriter writer = new StringWriter();
252 transformer.transform(new DOMSource(doc), new StreamResult(writer));
253 // return the modified String representation of the XML
254 return Optional.of(writer.toString().trim());