001package ca.uhn.fhir.util;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2021 Smile CDR, Inc.
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        @CoverageIgnore
059        @Override
060        public NamespaceContext getNamespaceContext() {
061                return myTarget.getNamespaceContext();
062        }
063
064        @CoverageIgnore
065        @Override
066        public String getPrefix(String theUri) throws XMLStreamException {
067                return myTarget.getPrefix(theUri);
068        }
069
070        @CoverageIgnore
071        @Override
072        public Object getProperty(String theName) throws IllegalArgumentException {
073                return myTarget.getProperty(theName);
074        }
075
076        @CoverageIgnore
077        @Override
078        public void setDefaultNamespace(String theUri) throws XMLStreamException {
079                myTarget.setDefaultNamespace(theUri);
080        }
081
082        @CoverageIgnore
083        @Override
084        public void setNamespaceContext(NamespaceContext theContext) throws XMLStreamException {
085                myTarget.setNamespaceContext(theContext);
086        }
087
088        @CoverageIgnore
089        @Override
090        public void setPrefix(String thePrefix, String theUri) throws XMLStreamException {
091                myTarget.setPrefix(thePrefix, theUri);
092        }
093
094        @Override
095        public void writeAttribute(String theLocalName, String theValue) throws XMLStreamException {
096                myTarget.writeAttribute(theLocalName, theValue);
097        }
098
099        @CoverageIgnore
100        @Override
101        public void writeAttribute(String theNamespaceURI, String theLocalName, String theValue) throws XMLStreamException {
102                myTarget.writeAttribute(theNamespaceURI, theLocalName, theValue);
103        }
104
105        @CoverageIgnore
106        @Override
107        public void writeAttribute(String thePrefix, String theNamespaceURI, String theLocalName, String theValue) throws XMLStreamException {
108                myTarget.writeAttribute(thePrefix, theNamespaceURI, theLocalName, theValue);
109        }
110
111        @CoverageIgnore
112        @Override
113        public void writeCData(String theData) throws XMLStreamException {
114                myTarget.writeCData(theData);
115        }
116
117        @Override
118        public void writeCharacters(char[] theText, int theStart, int theLen) throws XMLStreamException {
119                NonPrettyPrintWriterWrapper.writeCharacters(theText, theStart, theLen, myTarget, myInsidePre);
120        }
121
122        @Override
123        public void writeCharacters(String theText) throws XMLStreamException {
124                if (myInsidePre > 0) {
125                        myTarget.writeCharacters(theText);
126                } else {
127                        writeCharacters(theText.toCharArray(), 0, theText.length());
128                }
129        }
130
131        @Override
132        public void writeComment(String theData) throws XMLStreamException {
133                indent();
134                myTarget.writeComment(theData);
135        }
136
137        @Override
138        public void writeDefaultNamespace(String theNamespaceURI) throws XMLStreamException {
139                myTarget.writeDefaultNamespace(theNamespaceURI);
140        }
141
142        @CoverageIgnore
143        @Override
144        public void writeDTD(String theDtd) throws XMLStreamException {
145                myTarget.writeDTD(theDtd);
146        }
147
148        @CoverageIgnore
149        @Override
150        public void writeEmptyElement(String theLocalName) throws XMLStreamException {
151                indent();
152                myTarget.writeEmptyElement(theLocalName);
153        }
154
155        @CoverageIgnore
156        @Override
157        public void writeEmptyElement(String theNamespaceURI, String theLocalName) throws XMLStreamException {
158                indent();
159                myTarget.writeEmptyElement(theNamespaceURI, theLocalName);
160        }
161
162        @CoverageIgnore
163        @Override
164        public void writeEmptyElement(String thePrefix, String theLocalName, String theNamespaceURI) throws XMLStreamException {
165                indent();
166                myTarget.writeEmptyElement(thePrefix, theLocalName, theNamespaceURI);
167        }
168
169        @CoverageIgnore
170        @Override
171        public void writeEndDocument() throws XMLStreamException {
172                decrementAndIndent();
173                myTarget.writeEndDocument();
174        }
175
176        @Override
177        public void writeEndElement() throws XMLStreamException {
178                if (myInsidePre > 0) {
179                        myInsidePre--;
180                }
181                decrementAndIndent();
182
183                myTarget.writeEndElement();
184
185        }
186
187        @CoverageIgnore
188        @Override
189        public void writeEntityRef(String theName) throws XMLStreamException {
190                myTarget.writeEntityRef(theName);
191        }
192
193        @Override
194        public void writeNamespace(String thePrefix, String theNamespaceURI) throws XMLStreamException {
195                myTarget.writeNamespace(thePrefix, theNamespaceURI);
196        }
197
198        @CoverageIgnore
199        @Override
200        public void writeProcessingInstruction(String theTarget) throws XMLStreamException {
201                myTarget.writeProcessingInstruction(theTarget);
202        }
203
204        @CoverageIgnore
205        @Override
206        public void writeProcessingInstruction(String theTarget, String theData) throws XMLStreamException {
207                myTarget.writeProcessingInstruction(theTarget, theData);
208        }
209
210        @Override
211        public void writeStartDocument() throws XMLStreamException {
212                myFirstIndent=true;
213                myTarget.writeStartDocument();
214        }
215
216        @Override
217        public void writeStartDocument(String theVersion) throws XMLStreamException {
218                myFirstIndent=true;
219                myTarget.writeStartDocument(theVersion);
220        }
221
222        @Override
223        public void writeStartDocument(String theEncoding, String theVersion) throws XMLStreamException {
224                myFirstIndent=true;
225                myTarget.writeStartDocument(theEncoding, theVersion);
226        }
227
228        @Override
229        public void writeStartElement(String theLocalName) throws XMLStreamException {
230                indentAndAdd();
231                myTarget.writeStartElement(theLocalName);
232                if (PRE.equals(theLocalName) || myInsidePre > 0) {
233                        myInsidePre++;
234                }
235        }
236
237        @Override
238        public void writeStartElement(String theNamespaceURI, String theLocalName) throws XMLStreamException {
239                indentAndAdd();
240                myTarget.writeStartElement(theNamespaceURI, theLocalName);
241                if (PRE.equals(theLocalName) || myInsidePre > 0) {
242                        myInsidePre++;
243                }
244        }
245
246        @Override
247        public void writeStartElement(String thePrefix, String theLocalName, String theNamespaceURI) throws XMLStreamException {
248                indentAndAdd();
249                myTarget.writeStartElement(thePrefix, theLocalName, theNamespaceURI);
250                if (PRE.equals(theLocalName) || myInsidePre > 0) {
251                        myInsidePre++;
252                }
253        }
254
255        private void decrementAndIndent() throws XMLStreamException {
256                if (myInsidePre > 0) {
257                        return;
258                }
259                depth--;
260
261                if (hasChildElement.get(depth) == true) {
262                        // indent for current depth
263                        myTarget.writeCharacters(LINEFEED_CHAR + repeat(depth, INDENT_CHAR));
264                }
265        }
266
267        private void indent() throws XMLStreamException {
268                if (myFirstIndent) {
269                        myFirstIndent = false;
270                        return;
271                }
272                myTarget.writeCharacters(LINEFEED_CHAR + repeat(depth, INDENT_CHAR));
273        }
274
275        private void indentAndAdd() throws XMLStreamException {
276                if (myInsidePre > 0) {
277                        return;
278                }
279                indent();
280
281                // update state of parent node
282                if (depth > 0) {
283                        hasChildElement.put(depth - 1, true);
284                }
285
286                // reset state of current node
287                hasChildElement.put(depth, false);
288
289                depth++;
290        }
291
292        private String repeat(int d, String s) {
293                return StringUtils.repeat(s, d * 3);
294        }
295
296}