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}