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