2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6 * ================================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 * ============LICENSE_END=========================================================
21 package org.openecomp.mso.bpmn.core.xml;
23 import java.io.FileNotFoundException;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.InputStreamReader;
27 import java.io.Reader;
28 import java.io.StringReader;
29 import java.io.StringWriter;
30 import java.util.HashMap;
32 import java.util.Optional;
34 import javax.xml.parsers.DocumentBuilder;
35 import javax.xml.parsers.DocumentBuilderFactory;
36 import javax.xml.parsers.ParserConfigurationException;
37 import javax.xml.transform.OutputKeys;
38 import javax.xml.transform.Source;
39 import javax.xml.transform.Transformer;
40 import javax.xml.transform.TransformerException;
41 import javax.xml.transform.TransformerFactory;
42 import javax.xml.transform.dom.DOMSource;
43 import javax.xml.transform.stream.StreamResult;
44 import javax.xml.transform.stream.StreamSource;
45 import javax.xml.xpath.XPath;
46 import javax.xml.xpath.XPathConstants;
47 import javax.xml.xpath.XPathExpressionException;
48 import javax.xml.xpath.XPathFactory;
50 import org.openecomp.mso.logger.MsoLogger;
51 import org.w3c.dom.Document;
52 import org.w3c.dom.NamedNodeMap;
53 import org.w3c.dom.Node;
54 import org.w3c.dom.NodeList;
55 import org.xml.sax.InputSource;
56 import org.xml.sax.SAXException;
59 * XML transformation methods and other useful functions.
61 public final class XmlTool {
63 private static final Map<String, Integer> ENTITIES = new HashMap<String, Integer>();
64 private static final MsoLogger LOGGER = MsoLogger.getMsoLogger (MsoLogger.Catalog.BPEL);
66 ENTITIES.put("amp", new Integer(38));
67 ENTITIES.put("quot", new Integer(34));
68 ENTITIES.put("lt", new Integer(60));
69 ENTITIES.put("gt", new Integer(62));
73 * Normalizes and formats XML. This method consolidates and moves all namespace
74 * declarations to the root element. The result will not have an XML prolog or
76 * @param xml the XML to normalize
78 * @throws TransformerException
79 * @throws ParserConfigurationException
80 * @throws SAXException
81 * @throws XPathExpressionException
83 public static String normalize(Object xml) throws IOException, TransformerException,
84 ParserConfigurationException, SAXException, XPathExpressionException {
90 Source xsltSource = new StreamSource(new StringReader(
91 readResourceFile("normalize-namespaces.xsl")));
93 DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
94 dbFactory.setNamespaceAware(true);
95 DocumentBuilder db = dbFactory.newDocumentBuilder();
96 InputSource source = new InputSource(new StringReader(String.valueOf(xml)));
97 Document doc = db.parse(source);
99 // Start of code to remove whitespace outside of tags
100 XPath xPath = XPathFactory.newInstance().newXPath();
101 NodeList nodeList = (NodeList) xPath.evaluate(
102 "//text()[normalize-space()='']", doc, XPathConstants.NODESET);
104 for (int i = 0; i < nodeList.getLength(); ++i) {
105 Node node = nodeList.item(i);
106 node.getParentNode().removeChild(node);
108 // End of code to remove whitespace outside of tags
110 // the factory pattern supports different XSLT processors
111 TransformerFactory transformerFactory = TransformerFactory.newInstance();
112 Transformer transformer = transformerFactory.newTransformer(xsltSource);
114 transformer.setOutputProperty(OutputKeys.INDENT, "yes");
115 transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
116 transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
117 transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
119 StringWriter writer = new StringWriter();
120 transformer.transform(new DOMSource(doc), new StreamResult(writer));
121 return writer.toString().trim();
125 * Encodes a value so it can be used inside an XML text element.
126 * @param s the string to encode
127 * @return the encoded string
129 public static String encode(Object value) {
134 String s = String.valueOf(value);
135 StringBuilder out = new StringBuilder();
136 boolean modified = false;
138 for (int i = 0; i < s.length(); i++) {
139 char c = s.charAt(i);
144 } else if (c == '>') {
147 } else if (c == '&') {
150 } else if (c < 32 || c > 126) {
151 out.append("&#" + (int)c + ";");
159 return out.toString();
166 * Encodes a value so it can be used inside an XML attribute.
167 * @param s the string to encode
168 * @return the encoded string
170 public static String encodeAttr(Object value) {
175 String s = String.valueOf(value);
176 StringBuilder out = new StringBuilder();
177 boolean modified = false;
179 for (int i = 0; i < s.length(); i++) {
180 char c = s.charAt(i);
185 } else if (c == '>') {
188 } else if (c == '"') {
189 out.append(""");
191 } else if (c == '&') {
194 } else if (c < 32 || c > 126) {
195 out.append("&#" + (int)c + ";");
203 return out.toString();
210 * Decodes XML entities in a string value
211 * @param value a value with embedded XML entities
212 * @return the decoded string
214 public static String decode(Object value) {
219 String s = String.valueOf(value);
221 StringBuilder out = new StringBuilder(s.length());
222 int ampIndex = s.indexOf("&");
225 while (ampIndex >= 0) {
226 int nextAmpIndex = s.indexOf("&", ampIndex + 1);
227 int nextSemiIndex = s.indexOf(";", ampIndex + 1);
228 if (nextSemiIndex != -1 && (nextAmpIndex == -1 || nextSemiIndex < nextAmpIndex)) {
230 String entity = s.substring(ampIndex + 1, nextSemiIndex);
233 if (entity.startsWith("#")) {
234 code = Integer.parseInt(entity.substring(1), 10);
236 if (ENTITIES.containsKey(entity)) {
237 code = ENTITIES.get(entity);
240 } catch (NumberFormatException x) {
244 out.append(s.substring(lastEnd, ampIndex));
245 lastEnd = nextSemiIndex + 1;
246 if (code >= 0 && code <= 0xffff) {
247 out.append((char) code);
255 ampIndex = nextAmpIndex;
258 out.append(s.substring(lastEnd));
259 return out.toString();
263 * Removes the preamble, if present, from an XML document.
264 * @param xml the XML document
265 * @return a possibly modified document
267 public static String removePreamble(Object xml) {
272 return String.valueOf(xml).replaceAll("(<\\?[^<]*\\?>\\s*[\\r\\n]*)?", "");
276 * Removes namespaces and namespace declarations from an XML document.
277 * @param xml the XML document
278 * @return a possibly modified document
280 public static String removeNamespaces(Object xml) {
282 LOGGER.debug("removeNamespaces input object is null , returning null");
286 String text = String.valueOf(xml);
288 // remove xmlns declaration
289 text = text.replaceAll("xmlns.*?(\"|\').*?(\"|\')", "");
290 // remove opening tag prefix
291 text = text.replaceAll("(<)(\\w+:)(.*?>)", "$1$3");
292 // remove closing tags prefix
293 text = text.replaceAll("(</)(\\w+:)(.*?>)", "$1$3");
294 // remove extra spaces left when xmlns declarations are removed
295 text = text.replaceAll("\\s+>", ">");
302 * Reads the specified resource file and return the contents as a string.
303 * @param file Name of the resource file
304 * @return the contents of the resource file as a String
305 * @throws IOException if there is a problem reading the file
307 private static String readResourceFile(String file) throws IOException {
308 InputStream stream = null;
310 stream = XmlTool.class.getClassLoader().getResourceAsStream(file);
312 if (stream == null) {
313 throw new FileNotFoundException("No such resource file: " + file);
316 Reader reader = new InputStreamReader(stream, "UTF-8");
317 StringBuilder out = new StringBuilder();
318 char[] buf = new char[1024];
321 while ((n = reader.read(buf)) >= 0) {
322 out.append(buf, 0, n);
327 return out.toString();
329 if (stream != null) {
332 } catch (Exception e) {
333 LOGGER.debug("Exception at readResourceFile close stream: " + e);
340 * Parses the XML document String for the first occurrence of the specified element tag.
341 * If found, the value associated with that element tag is replaced with the new value
342 * and a String containing the modified XML document is returned. If the XML passed is
343 * null or the element tag is not found in the document, null will be returned.
344 * @param xml String containing the original XML document.
345 * @param elementTag String containing the tag of the element to be modified.
346 * @param newValue String containing the new value to be used to modify the corresponding element.
347 * @return the contents of the modified XML document as a String or null/empty if the modification failed.
348 * @throws IOException, TransformerException, ParserConfigurationException, SAXException
350 public static Optional<String> modifyElement(String xml, String elementTag, String newValue) throws IOException, TransformerException,
351 ParserConfigurationException, SAXException {
353 if (xml == null || xml.isEmpty()) {
354 // no XML content to be modified, return empty
355 return Optional.empty();
358 DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
359 dbFactory.setNamespaceAware(true);
360 DocumentBuilder db = dbFactory.newDocumentBuilder();
361 InputSource source = new InputSource(new StringReader(xml));
362 Document doc = db.parse(source);
364 Node modNode = doc.getElementsByTagName(elementTag).item(0);
365 if (modNode == null) {
366 // did not find the specified element to be modified, return empty
367 //System.out.println("Did not find element tag " + elementTag + " in XML");
368 return Optional.empty();
370 modNode.setTextContent(newValue);
373 TransformerFactory transformerFactory = TransformerFactory.newInstance();
374 Transformer transformer = transformerFactory.newTransformer();
375 StringWriter writer = new StringWriter();
376 transformer.transform(new DOMSource(doc), new StreamResult(writer));
377 // return the modified String representation of the XML
378 return Optional.of(writer.toString().trim());
382 * Instantiation is not allowed.