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.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54 import org.w3c.dom.Document;
55 import org.w3c.dom.Node;
56 import org.w3c.dom.NodeList;
57 import org.xml.sax.InputSource;
58 import org.xml.sax.SAXException;
61 * XML transformation methods and other useful functions.
63 public final class XmlTool {
65 private static final Map<String, Integer> ENTITIES = new HashMap<>();
66 private static final Logger logger = LoggerFactory.getLogger(XmlTool.class);
68 ENTITIES.put("amp", 38);
69 ENTITIES.put("quot", 34);
70 ENTITIES.put("lt", 60);
71 ENTITIES.put("gt", 62);
75 * Instantiation is not allowed.
80 * Normalizes and formats XML. This method consolidates and moves all namespace declarations to the root element.
81 * The result will not have an XML prolog or a trailing newline.
83 * @param xml the XML to normalize
85 * @throws TransformerException
86 * @throws ParserConfigurationException
87 * @throws SAXException
88 * @throws XPathExpressionException
90 public static String normalize(Object xml) throws IOException, TransformerException, ParserConfigurationException,
91 SAXException, XPathExpressionException {
97 Source xsltSource = new StreamSource(new StringReader(readResourceFile("normalize-namespaces.xsl")));
99 DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
100 dbFactory.setNamespaceAware(true);
101 dbFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
102 dbFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
103 DocumentBuilder db = dbFactory.newDocumentBuilder();
104 InputSource source = new InputSource(new StringReader(String.valueOf(xml)));
105 Document doc = db.parse(source);
107 // Start of code to remove whitespace outside of tags
108 XPath xPath = XPathFactory.newInstance().newXPath();
109 NodeList nodeList = (NodeList) xPath.evaluate("//text()[normalize-space()='']", doc, XPathConstants.NODESET);
111 for (int i = 0; i < nodeList.getLength(); ++i) {
112 Node node = nodeList.item(i);
113 node.getParentNode().removeChild(node);
115 // End of code to remove whitespace outside of tags
117 // the factory pattern supports different XSLT processors
118 TransformerFactory transformerFactory = TransformerFactory.newInstance();
119 transformerFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
120 Transformer transformer = transformerFactory.newTransformer(xsltSource);
122 transformer.setOutputProperty(OutputKeys.INDENT, "yes");
123 transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
124 transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
125 transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
127 StringWriter writer = new StringWriter();
128 transformer.transform(new DOMSource(doc), new StreamResult(writer));
129 return writer.toString().trim();
133 * Encodes a value so it can be used inside an XML text element.
135 * @param value the string to encode
136 * @return the encoded string
138 public static String encode(Object value) {
142 return StringEscapeUtils.escapeXml11(value.toString());
146 * Removes the preamble, if present, from an XML document.
148 * @param xml the XML document
149 * @return a possibly modified document
151 public static String removePreamble(Object xml) {
156 return String.valueOf(xml).replaceAll("(<\\?[^<]*\\?>\\s*[\\r\\n]*)?", "");
160 * Removes namespaces and namespace declarations from an XML document.
162 * @param xml the XML document
163 * @return a possibly modified document
165 public static String removeNamespaces(Object xml) {
167 logger.debug("removeNamespaces input object is null , returning null");
171 String text = String.valueOf(xml);
173 // remove xmlns declaration
174 text = text.replaceAll("xmlns.*?(\"|\').*?(\"|\')", "");
175 // remove opening tag prefix
176 text = text.replaceAll("(<)(\\w+:)(.*?>)", "$1$3");
177 // remove closing tags prefix
178 text = text.replaceAll("(</)(\\w+:)(.*?>)", "$1$3");
179 // remove extra spaces left when xmlns declarations are removed
180 text = text.replaceAll("\\s+>", ">");
187 * Reads the specified resource file and return the contents as a string.
189 * @param file Name of the resource file
190 * @return the contents of the resource file as a String
191 * @throws IOException if there is a problem reading the file
193 private static String readResourceFile(String file) throws IOException {
195 try (InputStream stream = XmlTool.class.getClassLoader().getResourceAsStream(file);
196 Reader reader = new InputStreamReader(stream, "UTF-8")) {
198 StringBuilder out = new StringBuilder();
199 char[] buf = new char[1024];
202 while ((n = reader.read(buf)) >= 0) {
203 out.append(buf, 0, n);
205 return out.toString();
206 } catch (Exception e) {
207 logger.debug("Exception at readResourceFile stream: " + e);
213 * Parses the XML document String for the first occurrence of the specified element tag. If found, the value
214 * associated with that element tag is replaced with the new value and a String containing the modified XML document
215 * is returned. If the XML passed is null or the element tag is not found in the document, null will be returned.
217 * @param xml String containing the original XML document.
218 * @param elementTag String containing the tag of the element to be modified.
219 * @param newValue String containing the new value to be used to modify the corresponding element.
220 * @return the contents of the modified XML document as a String or null/empty if the modification failed.
221 * @throws IOException, TransformerException, ParserConfigurationException, SAXException
223 public static Optional<String> modifyElement(String xml, String elementTag, String newValue)
224 throws IOException, TransformerException, ParserConfigurationException, SAXException {
226 if (xml == null || xml.isEmpty()) {
227 // no XML content to be modified, return empty
228 return Optional.empty();
231 DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
232 dbFactory.setNamespaceAware(true);
233 dbFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
234 dbFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
235 DocumentBuilder db = dbFactory.newDocumentBuilder();
236 InputSource source = new InputSource(new StringReader(xml));
237 Document doc = db.parse(source);
239 Node modNode = doc.getElementsByTagName(elementTag).item(0);
240 if (modNode == null) {
241 // did not find the specified element to be modified, return empty
242 // System.out.println("Did not find element tag " + elementTag + " in XML");
243 return Optional.empty();
245 modNode.setTextContent(newValue);
248 TransformerFactory transformerFactory = TransformerFactory.newInstance();
249 Transformer transformer = transformerFactory.newTransformer();
250 StringWriter writer = new StringWriter();
251 transformer.transform(new DOMSource(doc), new StreamResult(writer));
252 // return the modified String representation of the XML
253 return Optional.of(writer.toString().trim());