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 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 * ============LICENSE_END=========================================================
22 package org.openecomp.mso.bpmn.core.xml;
24 import java.io.FileNotFoundException;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.InputStreamReader;
28 import java.io.Reader;
29 import java.io.StringReader;
30 import java.io.StringWriter;
31 import java.util.HashMap;
33 import java.util.Optional;
35 import javax.xml.parsers.DocumentBuilder;
36 import javax.xml.parsers.DocumentBuilderFactory;
37 import javax.xml.parsers.ParserConfigurationException;
38 import javax.xml.transform.OutputKeys;
39 import javax.xml.transform.Source;
40 import javax.xml.transform.Transformer;
41 import javax.xml.transform.TransformerException;
42 import javax.xml.transform.TransformerFactory;
43 import javax.xml.transform.dom.DOMSource;
44 import javax.xml.transform.stream.StreamResult;
45 import javax.xml.transform.stream.StreamSource;
46 import javax.xml.xpath.XPath;
47 import javax.xml.xpath.XPathConstants;
48 import javax.xml.xpath.XPathExpressionException;
49 import javax.xml.xpath.XPathFactory;
51 import org.openecomp.mso.logger.MsoLogger;
52 import org.w3c.dom.Document;
53 import org.w3c.dom.NamedNodeMap;
54 import org.w3c.dom.Node;
55 import org.w3c.dom.NodeList;
56 import org.xml.sax.InputSource;
57 import org.xml.sax.SAXException;
60 * XML transformation methods and other useful functions.
62 public final class XmlTool {
64 private static final Map<String, Integer> ENTITIES = new HashMap<>();
65 private static final MsoLogger LOGGER = MsoLogger.getMsoLogger (MsoLogger.Catalog.BPEL);
67 ENTITIES.put("amp", new Integer(38));
68 ENTITIES.put("quot", new Integer(34));
69 ENTITIES.put("lt", new Integer(60));
70 ENTITIES.put("gt", new Integer(62));
74 * Instantiation is not allowed.
80 * Normalizes and formats XML. This method consolidates and moves all namespace
81 * declarations to the root element. The result will not have an XML prolog or
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,
91 ParserConfigurationException, SAXException, XPathExpressionException {
97 Source xsltSource = new StreamSource(new StringReader(
98 readResourceFile("normalize-namespaces.xsl")));
100 DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
101 dbFactory.setNamespaceAware(true);
102 DocumentBuilder db = dbFactory.newDocumentBuilder();
103 InputSource source = new InputSource(new StringReader(String.valueOf(xml)));
104 Document doc = db.parse(source);
106 // Start of code to remove whitespace outside of tags
107 XPath xPath = XPathFactory.newInstance().newXPath();
108 NodeList nodeList = (NodeList) xPath.evaluate(
109 "//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 Transformer transformer = transformerFactory.newTransformer(xsltSource);
121 transformer.setOutputProperty(OutputKeys.INDENT, "yes");
122 transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
123 transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
124 transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
126 StringWriter writer = new StringWriter();
127 transformer.transform(new DOMSource(doc), new StreamResult(writer));
128 return writer.toString().trim();
132 * Encodes a value so it can be used inside an XML text element.
133 * @param s the string to encode
134 * @return the encoded string
136 public static String encode(Object value) {
141 String s = String.valueOf(value);
142 StringBuilder out = new StringBuilder();
143 boolean modified = false;
145 for (int i = 0; i < s.length(); i++) {
146 char c = s.charAt(i);
151 } else if (c == '>') {
154 } else if (c == '&') {
157 } else if (c < 32 || c > 126) {
158 out.append("&#" + (int)c + ";");
166 return out.toString();
173 * Encodes a value so it can be used inside an XML attribute.
174 * @param s the string to encode
175 * @return the encoded string
177 public static String encodeAttr(Object value) {
182 String s = String.valueOf(value);
183 StringBuilder out = new StringBuilder();
184 boolean modified = false;
186 for (int i = 0; i < s.length(); i++) {
187 char c = s.charAt(i);
192 } else if (c == '>') {
195 } else if (c == '"') {
196 out.append(""");
198 } else if (c == '&') {
201 } else if (c < 32 || c > 126) {
202 out.append("&#" + (int)c + ";");
210 return out.toString();
217 * Decodes XML entities in a string value
218 * @param value a value with embedded XML entities
219 * @return the decoded string
221 public static String decode(Object value) {
226 String s = String.valueOf(value);
228 StringBuilder out = new StringBuilder(s.length());
229 int ampIndex = s.indexOf("&");
232 while (ampIndex >= 0) {
233 int nextAmpIndex = s.indexOf("&", ampIndex + 1);
234 int nextSemiIndex = s.indexOf(";", ampIndex + 1);
235 if (nextSemiIndex != -1 && (nextAmpIndex == -1 || nextSemiIndex < nextAmpIndex)) {
237 String entity = s.substring(ampIndex + 1, nextSemiIndex);
240 if (entity.startsWith("#")) {
241 code = Integer.parseInt(entity.substring(1), 10);
243 if (ENTITIES.containsKey(entity)) {
244 code = ENTITIES.get(entity);
247 } catch (NumberFormatException x) {
251 out.append(s.substring(lastEnd, ampIndex));
252 lastEnd = nextSemiIndex + 1;
253 if (code >= 0 && code <= 0xffff) {
254 out.append((char) code);
262 ampIndex = nextAmpIndex;
265 out.append(s.substring(lastEnd));
266 return out.toString();
270 * Removes the preamble, if present, from an XML document.
271 * @param xml the XML document
272 * @return a possibly modified document
274 public static String removePreamble(Object xml) {
279 return String.valueOf(xml).replaceAll("(<\\?[^<]*\\?>\\s*[\\r\\n]*)?", "");
283 * Removes namespaces and namespace declarations from an XML document.
284 * @param xml the XML document
285 * @return a possibly modified document
287 public static String removeNamespaces(Object xml) {
289 LOGGER.debug("removeNamespaces input object is null , returning null");
293 String text = String.valueOf(xml);
295 // remove xmlns declaration
296 text = text.replaceAll("xmlns.*?(\"|\').*?(\"|\')", "");
297 // remove opening tag prefix
298 text = text.replaceAll("(<)(\\w+:)(.*?>)", "$1$3");
299 // remove closing tags prefix
300 text = text.replaceAll("(</)(\\w+:)(.*?>)", "$1$3");
301 // remove extra spaces left when xmlns declarations are removed
302 text = text.replaceAll("\\s+>", ">");
309 * Reads the specified resource file and return the contents as a string.
310 * @param file Name of the resource file
311 * @return the contents of the resource file as a String
312 * @throws IOException if there is a problem reading the file
314 private static String readResourceFile(String file) throws IOException {
315 InputStream stream = null;
317 stream = XmlTool.class.getClassLoader().getResourceAsStream(file);
319 if (stream == null) {
320 throw new FileNotFoundException("No such resource file: " + file);
323 Reader reader = new InputStreamReader(stream, "UTF-8");
324 StringBuilder out = new StringBuilder();
325 char[] buf = new char[1024];
328 while ((n = reader.read(buf)) >= 0) {
329 out.append(buf, 0, n);
334 return out.toString();
336 if (stream != null) {
339 } catch (Exception e) {
340 LOGGER.debug("Exception at readResourceFile close stream: " + e);
347 * Parses the XML document String for the first occurrence of the specified element tag.
348 * If found, the value associated with that element tag is replaced with the new value
349 * and a String containing the modified XML document is returned. If the XML passed is
350 * null or the element tag is not found in the document, null will be returned.
351 * @param xml String containing the original XML document.
352 * @param elementTag String containing the tag of the element to be modified.
353 * @param newValue String containing the new value to be used to modify the corresponding element.
354 * @return the contents of the modified XML document as a String or null/empty if the modification failed.
355 * @throws IOException, TransformerException, ParserConfigurationException, SAXException
357 public static Optional<String> modifyElement(String xml, String elementTag, String newValue) throws IOException, TransformerException,
358 ParserConfigurationException, SAXException {
360 if (xml == null || xml.isEmpty()) {
361 // no XML content to be modified, return empty
362 return Optional.empty();
365 DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
366 dbFactory.setNamespaceAware(true);
367 DocumentBuilder db = dbFactory.newDocumentBuilder();
368 InputSource source = new InputSource(new StringReader(xml));
369 Document doc = db.parse(source);
371 Node modNode = doc.getElementsByTagName(elementTag).item(0);
372 if (modNode == null) {
373 // did not find the specified element to be modified, return empty
374 //System.out.println("Did not find element tag " + elementTag + " in XML");
375 return Optional.empty();
377 modNode.setTextContent(newValue);
380 TransformerFactory transformerFactory = TransformerFactory.newInstance();
381 Transformer transformer = transformerFactory.newTransformer();
382 StringWriter writer = new StringWriter();
383 transformer.transform(new DOMSource(doc), new StreamResult(writer));
384 // return the modified String representation of the XML
385 return Optional.of(writer.toString().trim());