001package ca.uhn.fhir.util;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2016 University Health Network
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 * 
013 *      http://www.apache.org/licenses/LICENSE-2.0
014 * 
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023import java.util.HashMap;
024import java.util.Map;
025
026import javax.xml.namespace.NamespaceContext;
027import javax.xml.stream.XMLStreamException;
028import javax.xml.stream.XMLStreamWriter;
029
030import org.apache.commons.lang3.StringUtils;
031
032public class PrettyPrintWriterWrapper implements XMLStreamWriter {
033
034        private static final String INDENT_CHAR = " ";
035        private static final String LINEFEED_CHAR = "\n";
036        private static final String PRE = "pre";
037        private int depth = 0;
038        private Map<Integer, Boolean> hasChildElement = new HashMap<Integer, Boolean>();
039
040        private int myInsidePre = 0;
041        private XMLStreamWriter myTarget;
042        private boolean myFirstIndent=true;
043
044        public PrettyPrintWriterWrapper(XMLStreamWriter target) {
045                myTarget = target;
046        }
047
048        @Override
049        public void close() throws XMLStreamException {
050                myTarget.close();
051        }
052
053        @Override
054        public void flush() throws XMLStreamException {
055                myTarget.flush();
056        }
057
058        @Override
059        public NamespaceContext getNamespaceContext() {
060                return myTarget.getNamespaceContext();
061        }
062
063        @Override
064        public String getPrefix(String theUri) throws XMLStreamException {
065                return myTarget.getPrefix(theUri);
066        }
067
068        @Override
069        public Object getProperty(String theName) throws IllegalArgumentException {
070                return myTarget.getProperty(theName);
071        }
072
073        @Override
074        public void setDefaultNamespace(String theUri) throws XMLStreamException {
075                myTarget.setDefaultNamespace(theUri);
076        }
077
078        @Override
079        public void setNamespaceContext(NamespaceContext theContext) throws XMLStreamException {
080                myTarget.setNamespaceContext(theContext);
081        }
082
083        @Override
084        public void setPrefix(String thePrefix, String theUri) throws XMLStreamException {
085                myTarget.setPrefix(thePrefix, theUri);
086        }
087
088        @Override
089        public void writeAttribute(String theLocalName, String theValue) throws XMLStreamException {
090                myTarget.writeAttribute(theLocalName, theValue);
091        }
092
093        @Override
094        public void writeAttribute(String theNamespaceURI, String theLocalName, String theValue) throws XMLStreamException {
095                myTarget.writeAttribute(theNamespaceURI, theLocalName, theValue);
096        }
097
098        @Override
099        public void writeAttribute(String thePrefix, String theNamespaceURI, String theLocalName, String theValue) throws XMLStreamException {
100                myTarget.writeAttribute(thePrefix, theNamespaceURI, theLocalName, theValue);
101        }
102
103        @Override
104        public void writeCData(String theData) throws XMLStreamException {
105                myTarget.writeCData(theData);
106        }
107
108        @Override
109        public void writeCharacters(char[] theText, int theStart, int theLen) throws XMLStreamException {
110                NonPrettyPrintWriterWrapper.writeCharacters(theText, theStart, theLen, myTarget, myInsidePre);
111        }
112
113        @Override
114        public void writeCharacters(String theText) throws XMLStreamException {
115                if (myInsidePre > 0) {
116                        myTarget.writeCharacters(theText);
117                } else {
118                        writeCharacters(theText.toCharArray(), 0, theText.length());
119                }
120        }
121
122        @Override
123        public void writeComment(String theData) throws XMLStreamException {
124                myTarget.writeComment(theData);
125        }
126
127        @Override
128        public void writeDefaultNamespace(String theNamespaceURI) throws XMLStreamException {
129                myTarget.writeDefaultNamespace(theNamespaceURI);
130        }
131
132        @Override
133        public void writeDTD(String theDtd) throws XMLStreamException {
134                myTarget.writeDTD(theDtd);
135        }
136
137        @Override
138        public void writeEmptyElement(String theLocalName) throws XMLStreamException {
139                indent();
140                myTarget.writeEmptyElement(theLocalName);
141        }
142
143        @Override
144        public void writeEmptyElement(String theNamespaceURI, String theLocalName) throws XMLStreamException {
145                indent();
146                myTarget.writeEmptyElement(theNamespaceURI, theLocalName);
147        }
148
149        @Override
150        public void writeEmptyElement(String thePrefix, String theLocalName, String theNamespaceURI) throws XMLStreamException {
151                indent();
152                myTarget.writeEmptyElement(thePrefix, theLocalName, theNamespaceURI);
153        }
154
155        @Override
156        public void writeEndDocument() throws XMLStreamException {
157                decrementAndIndent();
158                myTarget.writeEndDocument();
159        }
160
161        @Override
162        public void writeEndElement() throws XMLStreamException {
163                if (myInsidePre > 0) {
164                        myInsidePre--;
165                }
166                decrementAndIndent();
167
168                myTarget.writeEndElement();
169
170        }
171
172        @Override
173        public void writeEntityRef(String theName) throws XMLStreamException {
174                myTarget.writeEntityRef(theName);
175        }
176
177        @Override
178        public void writeNamespace(String thePrefix, String theNamespaceURI) throws XMLStreamException {
179                myTarget.writeNamespace(thePrefix, theNamespaceURI);
180        }
181
182        @Override
183        public void writeProcessingInstruction(String theTarget) throws XMLStreamException {
184                myTarget.writeProcessingInstruction(theTarget);
185        }
186
187        @Override
188        public void writeProcessingInstruction(String theTarget, String theData) throws XMLStreamException {
189                myTarget.writeProcessingInstruction(theTarget, theData);
190        }
191
192        @Override
193        public void writeStartDocument() throws XMLStreamException {
194                myFirstIndent=true;
195                myTarget.writeStartDocument();
196        }
197
198        @Override
199        public void writeStartDocument(String theVersion) throws XMLStreamException {
200                myFirstIndent=true;
201                myTarget.writeStartDocument(theVersion);
202        }
203
204        @Override
205        public void writeStartDocument(String theEncoding, String theVersion) throws XMLStreamException {
206                myFirstIndent=true;
207                myTarget.writeStartDocument(theEncoding, theVersion);
208        }
209
210        @Override
211        public void writeStartElement(String theLocalName) throws XMLStreamException {
212                indentAndAdd();
213                myTarget.writeStartElement(theLocalName);
214                if (PRE.equals(theLocalName) || myInsidePre > 0) {
215                        myInsidePre++;
216                }
217        }
218
219        @Override
220        public void writeStartElement(String theNamespaceURI, String theLocalName) throws XMLStreamException {
221                indentAndAdd();
222                myTarget.writeStartElement(theNamespaceURI, theLocalName);
223                if (PRE.equals(theLocalName) || myInsidePre > 0) {
224                        myInsidePre++;
225                }
226        }
227
228        @Override
229        public void writeStartElement(String thePrefix, String theLocalName, String theNamespaceURI) throws XMLStreamException {
230                indentAndAdd();
231                myTarget.writeStartElement(thePrefix, theLocalName, theNamespaceURI);
232                if (PRE.equals(theLocalName) || myInsidePre > 0) {
233                        myInsidePre++;
234                }
235        }
236
237        private void decrementAndIndent() throws XMLStreamException {
238                if (myInsidePre > 0) {
239                        return;
240                }
241                depth--;
242
243                if (hasChildElement.get(depth) == true) {
244                        // indent for current depth
245                        myTarget.writeCharacters(LINEFEED_CHAR + repeat(depth, INDENT_CHAR));
246                }
247        }
248
249        private void indent() throws XMLStreamException {
250                if (myFirstIndent) {
251                        myFirstIndent = false;
252                        return;
253                }
254                myTarget.writeCharacters(LINEFEED_CHAR + repeat(depth, INDENT_CHAR));
255        }
256
257        private void indentAndAdd() throws XMLStreamException {
258                if (myInsidePre > 0) {
259                        return;
260                }
261                indent();
262
263                // update state of parent node
264                if (depth > 0) {
265                        hasChildElement.put(depth - 1, true);
266                }
267
268                // reset state of current node
269                hasChildElement.put(depth, false);
270
271                depth++;
272        }
273
274        private String repeat(int d, String s) {
275                return StringUtils.repeat(s, d * 3);
276        }
277
278}