42114758b060870f7aef4a2ae64dab6f0a9061bb
[so.git] / bpmn / MSOCoreBPMN / src / main / java / org / openecomp / mso / bpmn / core / xml / XmlTool.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * OPENECOMP - MSO
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
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
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=========================================================
19  */
20
21 package org.openecomp.mso.bpmn.core.xml;
22
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;
31 import java.util.Map;
32
33 import javax.xml.parsers.DocumentBuilder;
34 import javax.xml.parsers.DocumentBuilderFactory;
35 import javax.xml.parsers.ParserConfigurationException;
36 import javax.xml.transform.OutputKeys;
37 import javax.xml.transform.Source;
38 import javax.xml.transform.Transformer;
39 import javax.xml.transform.TransformerException;
40 import javax.xml.transform.TransformerFactory;
41 import javax.xml.transform.dom.DOMSource;
42 import javax.xml.transform.stream.StreamResult;
43 import javax.xml.transform.stream.StreamSource;
44 import javax.xml.xpath.XPath;
45 import javax.xml.xpath.XPathConstants;
46 import javax.xml.xpath.XPathExpressionException;
47 import javax.xml.xpath.XPathFactory;
48
49 import org.w3c.dom.Document;
50 import org.w3c.dom.Node;
51 import org.w3c.dom.NodeList;
52 import org.xml.sax.InputSource;
53 import org.xml.sax.SAXException;
54
55 /**
56  * XML transformation methods and other useful functions.
57  */
58 public final class XmlTool {
59
60         private static final Map<String, Integer> ENTITIES = new HashMap<String, Integer>();
61
62         static {
63                 ENTITIES.put("amp", new Integer(38));
64                 ENTITIES.put("quot", new Integer(34));
65                 ENTITIES.put("lt", new Integer(60));
66                 ENTITIES.put("gt", new Integer(62));
67         }
68
69         /**
70          * Normalizes and formats XML.  This method consolidates and moves all namespace
71          * declarations to the root element.  The result will not have an XML prolog or
72          * a trailing newline.
73          * @param xml the XML to normalize
74          * @throws IOException 
75          * @throws TransformerException 
76          * @throws ParserConfigurationException 
77          * @throws SAXException 
78          * @throws XPathExpressionException 
79          */
80         public static String normalize(Object xml) throws IOException, TransformerException,
81                         ParserConfigurationException, SAXException, XPathExpressionException {
82                 
83                 if (xml == null) {
84                         return null;
85                 }
86
87                 Source xsltSource = new StreamSource(new StringReader(
88                         readResourceFile("normalize-namespaces.xsl")));
89
90                 DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
91                 dbFactory.setNamespaceAware(true);
92                 DocumentBuilder db = dbFactory.newDocumentBuilder();
93                 InputSource source = new InputSource(new StringReader(String.valueOf(xml)));
94                 Document doc = db.parse(source);
95
96                 // Start of code to remove whitespace outside of tags
97                 XPath xPath = XPathFactory.newInstance().newXPath();
98                 NodeList nodeList = (NodeList) xPath.evaluate(
99                         "//text()[normalize-space()='']", doc, XPathConstants.NODESET);
100
101                 for (int i = 0; i < nodeList.getLength(); ++i) {
102                         Node node = nodeList.item(i);
103                         node.getParentNode().removeChild(node);
104                 }
105                 // End of code to remove whitespace outside of tags
106
107                 // the factory pattern supports different XSLT processors
108                 TransformerFactory transformerFactory = TransformerFactory.newInstance();
109                 Transformer transformer = transformerFactory.newTransformer(xsltSource);
110
111                 transformer.setOutputProperty(OutputKeys.INDENT, "yes");
112                 transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
113                 transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
114                 transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
115
116                 StringWriter writer = new StringWriter();
117                 transformer.transform(new DOMSource(doc), new StreamResult(writer));
118                 return writer.toString().trim();
119         }
120
121         /**
122          * Encodes a value so it can be used inside an XML text element.
123          * @param s the string to encode
124          * @return the encoded string
125          */
126         public static String encode(Object value) {
127                 if (value == null) {
128                         return null;
129                 }
130
131                 String s = String.valueOf(value);
132                 StringBuilder out = new StringBuilder();
133                 boolean modified = false;
134
135                 for (int i = 0; i < s.length(); i++) {
136                         char c = s.charAt(i);
137
138                         if (c == '<') {
139                                 out.append("&lt;");
140                                 modified = true;
141                         } else if (c == '>') {
142                                 out.append("&gt;");
143                                 modified = true;
144                         } else if (c == '&') {
145                                 out.append("&amp;");
146                                 modified = true;
147                         } else if (c < 32 || c > 126) {
148                                 out.append("&#" + (int)c + ";");
149                                 modified = true;
150                         } else {
151                                 out.append(c);
152                         }
153                 }
154
155                 if (modified) {
156                         return out.toString();
157                 } else {
158                         return s;
159                 }
160         }
161         
162         /**
163          * Encodes a value so it can be used inside an XML attribute.
164          * @param s the string to encode
165          * @return the encoded string
166          */
167         public static String encodeAttr(Object value) {
168                 if (value == null) {
169                         return null;
170                 }
171
172                 String s = String.valueOf(value);
173                 StringBuilder out = new StringBuilder();
174                 boolean modified = false;
175
176                 for (int i = 0; i < s.length(); i++) {
177                         char c = s.charAt(i);
178
179                         if (c == '<') {
180                                 out.append("&lt;");
181                                 modified = true;
182                         } else if (c == '>') {
183                                 out.append("&gt;");
184                                 modified = true;
185                         } else if (c == '"') {
186                                 out.append("&quot;");
187                                 modified = true;
188                         } else if (c == '&') {
189                                 out.append("&amp;");
190                                 modified = true;
191                         } else if (c < 32 || c > 126) {
192                                 out.append("&#" + (int)c + ";");
193                                 modified = true;
194                         } else {
195                                 out.append(c);
196                         }
197                 }
198
199                 if (modified) {
200                         return out.toString();
201                 } else {
202                         return s;
203                 }
204         }
205         
206         /**
207          * Decodes XML entities in a string value
208          * @param value a value with embedded XML entities
209          * @return the decoded string
210          */
211         public static String decode(Object value) {
212                 if (value == null) {
213                         return null;
214                 }
215                 
216                 String s = String.valueOf(value);
217
218                 StringBuilder out = new StringBuilder(s.length());
219                 int ampIndex = s.indexOf("&");
220                 int lastEnd = 0;
221
222                 while (ampIndex >= 0) {
223                         int nextAmpIndex = s.indexOf("&", ampIndex + 1);
224                         int nextSemiIndex = s.indexOf(";", ampIndex + 1);
225                         if (nextSemiIndex != -1 && (nextAmpIndex == -1 || nextSemiIndex < nextAmpIndex)) {
226                                 int code = -1;
227                                 String entity = s.substring(ampIndex + 1, nextSemiIndex);
228
229                                 try {
230                                         if (entity.startsWith("#")) {
231                                                 code = Integer.parseInt(entity.substring(1), 10);
232                                         } else {
233                                                 if (ENTITIES.containsKey(entity)) {
234                                                         code = ENTITIES.get(entity);
235                                                 }
236                                         }
237                                 } catch (NumberFormatException x) {
238                                         // Do nothing
239                                 }
240
241                                 out.append(s.substring(lastEnd, ampIndex));
242                                 lastEnd = nextSemiIndex + 1;
243                                 if (code >= 0 && code <= 0xffff) {
244                                         out.append((char) code);
245                                 } else {
246                                         out.append("&");
247                                         out.append(entity);
248                                         out.append(";");
249                                 }
250                         }
251
252                         ampIndex = nextAmpIndex;
253                 }
254
255                 out.append(s.substring(lastEnd));
256                 return out.toString();
257         }
258
259         /**
260          * Removes the preamble, if present, from an XML document.
261          * @param xml the XML document
262          * @return a possibly modified document
263          */
264         public static String removePreamble(Object xml) {
265                 if (xml == null) {
266                         return null;
267                 }
268
269                 return String.valueOf(xml).replaceAll("(<\\?[^<]*\\?>\\s*[\\r\\n]*)?", "");
270         }
271
272         /**
273          * Removes namespaces and namespace declarations from an XML document.
274          * @param xml the XML document
275          * @return a possibly modified document
276          */
277         public static String removeNamespaces(Object xml) {
278                 if (xml == null) {
279                         return null;
280                 }
281
282                 String text = String.valueOf(xml);
283
284                 // remove xmlns declaration
285                 text = text.replaceAll("xmlns.*?(\"|\').*?(\"|\')", "");
286                 // remove opening tag prefix
287                 text = text.replaceAll("(<)(\\w+:)(.*?>)", "$1$3");
288                 // remove closing tags prefix
289                 text = text.replaceAll("(</)(\\w+:)(.*?>)", "$1$3");
290                 // remove extra spaces left when xmlns declarations are removed
291                 text = text.replaceAll("\\s+>", ">");
292
293                 return text;
294         }
295
296
297         /**
298          * Reads the specified resource file and return the contents as a string.
299          * @param file Name of the resource file
300          * @return the contents of the resource file as a String
301          * @throws IOException if there is a problem reading the file
302          */
303         private static String readResourceFile(String file) throws IOException {
304                 InputStream stream = null;
305                 try {
306                         stream = XmlTool.class.getClassLoader().getResourceAsStream(file);
307
308                         if (stream == null) {
309                                 throw new FileNotFoundException("No such resource file: " + file);
310                         }
311
312                         Reader reader = new InputStreamReader(stream, "UTF-8");
313                         StringBuilder out = new StringBuilder();
314                         char[] buf = new char[1024];
315                         int n;
316
317                         while ((n = reader.read(buf)) >= 0) {
318                                 out.append(buf, 0, n);
319                         }
320
321                         stream.close();
322                         stream = null;
323                         return out.toString();
324                 } finally {
325                         if (stream != null) {
326                                 try {
327                                         stream.close();
328                                 } catch (Exception e) {
329                                         // Ignore
330                                 }
331                         }
332                 }
333         }
334         
335         /**
336          * Instantiation is not allowed.
337          */
338         private XmlTool() {
339         }
340 }