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}