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}