001/*
002 * #%L
003 * HAPI FHIR - Core Library
004 * %%
005 * Copyright (C) 2014 - 2024 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 org.apache.commons.lang3.StringUtils;
023
024import java.util.HashMap;
025import java.util.Map;
026import javax.xml.namespace.NamespaceContext;
027import javax.xml.stream.XMLStreamException;
028import javax.xml.stream.XMLStreamWriter;
029
030public class PrettyPrintWriterWrapper implements XMLStreamWriter {
031
032        private static final String INDENT_CHAR = " ";
033        private static final String LINEFEED_CHAR = "\n";
034        private static final String PRE = "pre";
035        private int depth = 0;
036        private Map<Integer, Boolean> hasChildElement = new HashMap<Integer, Boolean>();
037
038        private int myInsidePre = 0;
039        private XMLStreamWriter myTarget;
040        private boolean myFirstIndent = true;
041
042        public PrettyPrintWriterWrapper(XMLStreamWriter target) {
043                myTarget = target;
044        }
045
046        @Override
047        public void close() throws XMLStreamException {
048                myTarget.close();
049        }
050
051        @Override
052        public void flush() throws XMLStreamException {
053                myTarget.flush();
054        }
055
056        @CoverageIgnore
057        @Override
058        public NamespaceContext getNamespaceContext() {
059                return myTarget.getNamespaceContext();
060        }
061
062        @CoverageIgnore
063        @Override
064        public String getPrefix(String theUri) throws XMLStreamException {
065                return myTarget.getPrefix(theUri);
066        }
067
068        @CoverageIgnore
069        @Override
070        public Object getProperty(String theName) throws IllegalArgumentException {
071                return myTarget.getProperty(theName);
072        }
073
074        @CoverageIgnore
075        @Override
076        public void setDefaultNamespace(String theUri) throws XMLStreamException {
077                myTarget.setDefaultNamespace(theUri);
078        }
079
080        @CoverageIgnore
081        @Override
082        public void setNamespaceContext(NamespaceContext theContext) throws XMLStreamException {
083                myTarget.setNamespaceContext(theContext);
084        }
085
086        @CoverageIgnore
087        @Override
088        public void setPrefix(String thePrefix, String theUri) throws XMLStreamException {
089                myTarget.setPrefix(thePrefix, theUri);
090        }
091
092        @Override
093        public void writeAttribute(String theLocalName, String theValue) throws XMLStreamException {
094                myTarget.writeAttribute(theLocalName, theValue);
095        }
096
097        @CoverageIgnore
098        @Override
099        public void writeAttribute(String theNamespaceURI, String theLocalName, String theValue) throws XMLStreamException {
100                myTarget.writeAttribute(theNamespaceURI, theLocalName, theValue);
101        }
102
103        @CoverageIgnore
104        @Override
105        public void writeAttribute(String thePrefix, String theNamespaceURI, String theLocalName, String theValue)
106                        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)
164                        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        @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)
247                        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}