blob: 22867474c39b5a40de12ed26b69ba1513a2a6bbc [file] [log] [blame]
Yuta HIGUCHIb34078e2017-08-17 12:06:02 -07001/*
2 * Copyright 2017-present Open Networking Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.onlab.util;
17
18import java.io.IOException;
19import java.io.StringWriter;
20
Yuta HIGUCHI7da7e622018-02-13 20:24:51 -080021import javax.xml.parsers.DocumentBuilder;
Yuta HIGUCHIb34078e2017-08-17 12:06:02 -070022import javax.xml.parsers.DocumentBuilderFactory;
23import javax.xml.transform.OutputKeys;
24import javax.xml.transform.Transformer;
25import javax.xml.transform.TransformerFactory;
26import javax.xml.transform.dom.DOMSource;
27import javax.xml.transform.stream.StreamResult;
28import javax.xml.xpath.XPath;
29import javax.xml.xpath.XPathConstants;
30import javax.xml.xpath.XPathFactory;
31
32import org.slf4j.Logger;
33import org.slf4j.LoggerFactory;
34import org.w3c.dom.Document;
35import org.w3c.dom.Node;
36import org.w3c.dom.NodeList;
37import org.xml.sax.InputSource;
Yuta HIGUCHI7da7e622018-02-13 20:24:51 -080038import org.xml.sax.SAXException;
39import org.xml.sax.helpers.DefaultHandler;
40
Yuta HIGUCHIb34078e2017-08-17 12:06:02 -070041import com.google.common.base.Supplier;
42import com.google.common.base.Suppliers;
43import com.google.common.io.CharSource;
44
45/**
46 * PrettyPrinted XML String.
47 */
48public class XmlString implements CharSequence {
49
50 private static final Logger log = LoggerFactory.getLogger(XmlString.class);
51
52 private final Supplier<String> prettyString;
53
54
55 /**
56 * Prettifies given XML String.
57 *
58 * @param xml input XML
59 * @return prettified input or input itself is input is not well-formed
60 */
61 public static CharSequence prettifyXml(CharSequence xml) {
62 return new XmlString(CharSource.wrap(xml));
63 }
64
65 XmlString(CharSource inputXml) {
66 prettyString = Suppliers.memoize(() -> prettyPrintXml(inputXml));
67 }
68
69 private String prettyPrintXml(CharSource inputXml) {
70 try {
Yuta HIGUCHI7da7e622018-02-13 20:24:51 -080071 Document document;
72 boolean wasFragment = false;
73
74 DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance()
75 .newDocumentBuilder();
76 // do not print error to stderr
77 docBuilder.setErrorHandler(new DefaultHandler());
78
79 try {
80 document = docBuilder
81 .parse(new InputSource(inputXml.openStream()));
82 } catch (SAXException e) {
83 log.debug("will retry assuming input is XML fragment", e);
84 // attempt to parse XML fragments, adding virtual root
85 try {
86 document = docBuilder
87 .parse(new InputSource(CharSource.concat(CharSource.wrap("<vroot>"),
88 inputXml,
89 CharSource.wrap("</vroot>")
90 ).openStream()));
91 wasFragment = true;
92 } catch (SAXException e1) {
93 log.debug("SAXException after retry", e1);
94 // Probably wasn't fragment issue, throwing original
95 throw e;
96 }
97 }
Yuta HIGUCHIb34078e2017-08-17 12:06:02 -070098
99 document.normalize();
100
101 XPath xPath = XPathFactory.newInstance().newXPath();
102 NodeList nodeList = (NodeList) xPath.evaluate("//text()[normalize-space()='']",
103 document,
104 XPathConstants.NODESET);
105
106 for (int i = 0; i < nodeList.getLength(); ++i) {
107 Node node = nodeList.item(i);
108 node.getParentNode().removeChild(node);
109 }
110
111 // Setup pretty print options
112 Transformer t = TransformerFactory.newInstance().newTransformer();
113 t.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
114 t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
115 t.setOutputProperty(OutputKeys.INDENT, "yes");
116 t.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
117
118 // Return pretty print xml string
119 StringWriter strWriter = new StringWriter();
Yuta HIGUCHI7da7e622018-02-13 20:24:51 -0800120 if (wasFragment) {
121 // print everything but virtual root node added
122 NodeList children = document.getDocumentElement().getChildNodes();
123 for (int i = 0; i < children.getLength(); ++i) {
124 t.transform(new DOMSource(children.item(i)), new StreamResult(strWriter));
125 }
126 } else {
127 t.transform(new DOMSource(document), new StreamResult(strWriter));
128 }
Yuta HIGUCHIb34078e2017-08-17 12:06:02 -0700129 return strWriter.toString();
130 } catch (Exception e) {
131 log.warn("Pretty printing failed", e);
132 try {
133 String rawInput = inputXml.read();
134 log.debug(" failed input: \n{}", rawInput);
135 return rawInput;
136 } catch (IOException e1) {
137 log.error("Failed to read from input", e1);
138 return inputXml.toString();
139 }
140 }
141 }
142
143 @Override
144 public int length() {
145 return toString().length();
146 }
147
148 @Override
149 public char charAt(int index) {
150 return toString().charAt(index);
151 }
152
153 @Override
154 public CharSequence subSequence(int start, int end) {
155 return toString().subSequence(start, end);
156 }
157
158 @Override
159 public String toString() {
160 return prettyString.get();
161 }
162
163}