001/** 002 * 003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved. 004 * 005 * This library is free software; you can redistribute it and/or 006 * modify it under the terms of the GNU Lesser General Public 007 * License as published by the Free Software Foundation; either 008 * version 2.1 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 * Lesser General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public 016 * License along with this library. If not, see <http://www.gnu.org/licenses/>. 017 * 018 **/ 019package lucee.runtime.writer; 020 021import java.io.IOException; 022 023import javax.servlet.http.HttpServletRequest; 024import javax.servlet.http.HttpServletResponse; 025 026import lucee.runtime.PageContext; 027 028/** 029 * JSP Writer that Remove WhiteSpace from given content while preserving pre-formatted spaces 030 * in Tags like <CODE> <PRE> and <TEXTAREA> 031 */ 032public final class CFMLWriterWSPref extends CFMLWriterImpl implements WhiteSpaceWriter { 033 034 public static final char CHAR_NL = '\n'; 035 public static final char CHAR_RETURN = '\r'; 036 037 private static final char CHAR_GT = '>'; 038 private static final char CHAR_LT = '<'; 039 private static final char CHAR_SL = '/'; 040 private static final String[] EXCLUDE_TAGS = { "code", "pre", "textarea" }; 041 042 private static int minTagLen = 64; 043 044 private int[] depths; 045 private int depthSum = 0; 046 private char lastChar = 0; 047 private boolean isFirstChar = true; 048 private StringBuilder sb = new StringBuilder(); 049 050 051 static { 052 053 // TODO: set EXCLUDE_TAGS to values from WebConfigImpl 054 055 for ( String s : EXCLUDE_TAGS ) 056 if ( s.length() < minTagLen ) 057 minTagLen = s.length(); 058 059 minTagLen++; // add 1 for LessThan symbol 060 } 061 062 063 /** 064 * constructor of the class 065 * @param rsp 066 * @param bufferSize 067 * @param autoFlush 068 */ 069 public CFMLWriterWSPref(PageContext pc, HttpServletRequest req, HttpServletResponse rsp, int bufferSize, boolean autoFlush, boolean closeConn, 070 boolean showVersion, boolean contentLength) { 071 super(pc,req,rsp, bufferSize, autoFlush,closeConn,showVersion,contentLength); 072 depths = new int[ EXCLUDE_TAGS.length ]; 073 } 074 075 076 /** 077 * prints the characters from the buffer and resets it 078 * 079 * TODO: make sure that printBuffer() is called at the end of the stream in case we have some characters there! (flush() ?) 080 */ 081 synchronized void printBuffer() throws IOException { // TODO: is synchronized really needed here? 082 int len = sb.length(); 083 if ( len > 0 ) { 084 char[] chars = new char[ len ]; 085 sb.getChars( 0, len, chars, 0 ); 086 sb.setLength( 0 ); 087 super.write( chars, 0, chars.length ); 088 } 089 } 090 091 void printBufferEL() { 092 if( sb.length() > 0 ) { 093 try { 094 printBuffer(); 095 } 096 catch (IOException e) {} 097 } 098 } 099 100 /** 101 * checks if a character is part of an open html tag or close html tag, and if so adds it to the buffer, otherwise returns false. 102 * 103 * @param c 104 * @return true if the char was added to the buffer, false otherwise 105 */ 106 boolean addToBuffer( char c ) throws IOException { 107 int len = sb.length(); 108 if ( len == 0 && c != CHAR_LT ) 109 return false; // buffer must starts with '<' 110 111 sb.append( c ); // if we reached this point then we will return true 112 if ( ++len >= minTagLen ) { // increment len as it was sampled before we appended c 113 boolean isClosingTag = ( len >= 2 && sb.charAt( 1 ) == CHAR_SL ); 114 String substr; 115 if ( isClosingTag ) 116 substr = sb.substring( 2 ); // we know that the 1st two chars are "</" 117 else 118 substr = sb.substring( 1 ); // we know that the 1st char is "<" 119 for ( int i=0; i<EXCLUDE_TAGS.length; i++ ) { // loop thru list of WS-preserving tags 120 if ( substr.equalsIgnoreCase( EXCLUDE_TAGS[ i ] ) ) { // we have a match 121 if ( isClosingTag ) { 122 depthDec( i ); // decrement the depth at i and calc depthSum 123 printBuffer(); 124 lastChar = 0; // needed to allow WS after buffer was printed 125 } else { 126 depthInc( i ); // increment the depth at i and calc depthSum 127 } 128 } 129 } 130 } 131 return true; 132 } 133 134 /** 135 * decrement the depth at index and calc the new depthSum 136 * @param index 137 */ 138 private void depthDec( int index ) { 139 if ( --depths[ index ] < 0 ) 140 depths[ index ] = 0; 141 depthCalc(); 142 } 143 144 /** 145 * increment the depth at index and calc the new depthSum 146 * @param index 147 */ 148 private void depthInc( int index ) { 149 depths[ index ]++; 150 depthCalc(); 151 } 152 153 /** 154 * calc the new depthSum 155 */ 156 private void depthCalc() { 157 int sum = 0; 158 for ( int d : depths ) 159 sum += d; 160 depthSum = sum; 161 } 162 163 164 /** 165 * sends a character to output stream if it is not a consecutive white-space unless we're inside a PRE or TEXTAREA tag. 166 * 167 * @param c 168 * @throws IOException 169 */ 170 @Override 171 public void print( char c ) throws IOException { 172 boolean isWS = Character.isWhitespace( c ); 173 if ( isWS ) { 174 if ( isFirstChar ) // ignore all WS before non-WS content 175 return; 176 if ( c == CHAR_RETURN ) // ignore Carriage-Return chars 177 return; 178 if ( sb.length() > 0 ) { 179 printBuffer(); // buffer should never contain WS so flush it 180 lastChar = (c == CHAR_NL) ? CHAR_NL : c; 181 super.print( lastChar ); 182 return; 183 } 184 } 185 186 isFirstChar = false; 187 if ( c == CHAR_GT && sb.length() > 0 ) 188 printBuffer(); // buffer should never contain ">" so flush it 189 190 if ( isWS || !addToBuffer( c ) ) { 191 if ( depthSum == 0 ) { // we're not in a WS-preserving tag; suppress whitespace 192 if ( isWS ) { // this char is WS 193 if ( lastChar == CHAR_NL ) // lastChar was NL; discard this WS char 194 return; 195 if ( c != CHAR_NL ) { // this WS char is not NL 196 if ( Character.isWhitespace( lastChar ) ) 197 return; // lastChar was WS but Not NL; discard this WS char 198 } 199 } 200 } 201 lastChar = c; // remember c as lastChar and write it to output stream 202 super.print( c ); 203 } 204 } 205 206 207 /** 208 * @see lucee.runtime.writer.CFMLWriter#writeRaw(java.lang.String) 209 */ 210 public void writeRaw(String str) throws IOException { 211 printBuffer(); 212 super.write(str); 213 } 214 215 /** 216 * just a wrapper function for ACF 217 * @throws IOException 218 */ 219 public void initHeaderBuffer() throws IOException{ 220 resetHTMLHead(); 221 } 222 223 224 225 226 227 228 229 230 231 232 233 234 /** 235 * @see lucee.runtime.writer.CFMLWriterImpl#clear() 236 */ 237 public final void clear() throws IOException { 238 printBuffer(); 239 super.clear(); 240 } 241 242 /** 243 * @see lucee.runtime.writer.CFMLWriterImpl#clearBuffer() 244 */ 245 public final void clearBuffer() { 246 printBufferEL(); 247 super.clearBuffer(); 248 } 249 250 /** 251 * @see lucee.runtime.writer.CFMLWriterImpl#close() 252 */ 253 public final void close() throws IOException { 254 printBuffer(); 255 super.close(); 256 } 257 258 /** 259 * @see lucee.runtime.writer.CFMLWriterImpl#flush() 260 */ 261 public final void flush() throws IOException { 262 printBuffer(); 263 super.flush(); 264 } 265 266 /** 267 * @see lucee.runtime.writer.CFMLWriterImpl#getRemaining() 268 */ 269 public final int getRemaining() { 270 printBufferEL(); 271 return super.getRemaining(); 272 } 273 274 /** 275 * @see lucee.runtime.writer.CFMLWriterImpl#newLine() 276 */ 277 public final void newLine() throws IOException { 278 print(CHAR_NL); 279 } 280 281 /** 282 * @see lucee.runtime.writer.CFMLWriterImpl#print(boolean) 283 */ 284 public final void print(boolean b) throws IOException { 285 printBuffer(); 286 super.print(b); 287 } 288 289 /** 290 * @see lucee.runtime.writer.CFMLWriterImpl#print(char[]) 291 */ 292 public final void print(char[] chars) throws IOException { 293 write(chars,0,chars.length); 294 } 295 296 /** 297 * @see lucee.runtime.writer.CFMLWriterImpl#print(double) 298 */ 299 public final void print(double d) throws IOException { 300 printBuffer(); 301 super.print(d); 302 } 303 304 /** 305 * @see lucee.runtime.writer.CFMLWriterImpl#print(float) 306 */ 307 public final void print(float f) throws IOException { 308 printBuffer(); 309 super.print(f); 310 } 311 312 /** 313 * @see lucee.runtime.writer.CFMLWriterImpl#print(int) 314 */ 315 public final void print(int i) throws IOException { 316 printBuffer(); 317 super.print(i); 318 } 319 320 /** 321 * @see lucee.runtime.writer.CFMLWriterImpl#print(long) 322 */ 323 public final void print(long l) throws IOException { 324 printBuffer(); 325 super.print(l); 326 } 327 328 /** 329 * @see lucee.runtime.writer.CFMLWriterImpl#print(java.lang.Object) 330 */ 331 public final void print(Object obj) throws IOException { 332 print(obj.toString()); 333 } 334 335 /** 336 * @see lucee.runtime.writer.CFMLWriterImpl#print(java.lang.String) 337 */ 338 public final void print(String str) throws IOException { 339 write(str.toCharArray(),0,str.length()); 340 } 341 342 /** 343 * @see lucee.runtime.writer.CFMLWriterImpl#println() 344 */ 345 public final void println() throws IOException { 346 print(CHAR_NL); 347 } 348 349 /** 350 * @see lucee.runtime.writer.CFMLWriterImpl#println(boolean) 351 */ 352 public final void println(boolean b) throws IOException { 353 printBuffer(); 354 super.print(b); 355 print(CHAR_NL); 356 } 357 358 /** 359 * @see lucee.runtime.writer.CFMLWriterImpl#println(char) 360 */ 361 public final void println(char c) throws IOException { 362 print(c); 363 print(CHAR_NL); 364 } 365 366 /** 367 * @see lucee.runtime.writer.CFMLWriterImpl#println(char[]) 368 */ 369 public final void println(char[] chars) throws IOException { 370 write(chars,0,chars.length); 371 print(CHAR_NL); 372 } 373 374 /** 375 * @see lucee.runtime.writer.CFMLWriterImpl#println(double) 376 */ 377 public final void println(double d) throws IOException { 378 printBuffer(); 379 super.print(d); 380 print(CHAR_NL); 381 } 382 383 /** 384 * @see lucee.runtime.writer.CFMLWriterImpl#println(float) 385 */ 386 public final void println(float f) throws IOException { 387 printBuffer(); 388 super.print(f); 389 print(CHAR_NL); 390 } 391 392 /** 393 * @see lucee.runtime.writer.CFMLWriterImpl#println(int) 394 */ 395 public final void println(int i) throws IOException { 396 printBuffer(); 397 super.print(i); 398 print(CHAR_NL); 399 } 400 401 /** 402 * @see lucee.runtime.writer.CFMLWriterImpl#println(long) 403 */ 404 public final void println(long l) throws IOException { 405 printBuffer(); 406 super.print(l); 407 print(CHAR_NL); 408 } 409 410 /** 411 * @see lucee.runtime.writer.CFMLWriterImpl#println(java.lang.Object) 412 */ 413 public final void println(Object obj) throws IOException { 414 println(obj.toString()); 415 } 416 417 /** 418 * @see lucee.runtime.writer.CFMLWriterImpl#println(java.lang.String) 419 */ 420 public final void println(String str) throws IOException { 421 print(str); 422 print(CHAR_NL); 423 424 } 425 426 /** 427 * @see lucee.runtime.writer.CFMLWriterImpl#write(char[], int, int) 428 */ 429 public final void write(char[] chars, int off, int len) throws IOException { 430 for(int i=off;i<len;i++) { 431 print(chars[i]); 432 } 433 } 434 435 /** 436 * @see lucee.runtime.writer.CFMLWriterImpl#write(java.lang.String, int, int) 437 */ 438 public final void write(String str, int off, int len) throws IOException { 439 write(str.toCharArray(),off,len); 440 } 441 442 443 /** 444 * @see lucee.runtime.writer.CFMLWriterImpl#write(char[]) 445 */ 446 public final void write(char[] chars) throws IOException { 447 write(chars,0,chars.length); 448 } 449 450 /** 451 * @see lucee.runtime.writer.CFMLWriterImpl#write(int) 452 */ 453 public final void write(int i) throws IOException { 454 print(i); 455 } 456 457 /** 458 * @see lucee.runtime.writer.CFMLWriterImpl#write(java.lang.String) 459 */ 460 public final void write(String str) throws IOException { 461 write(str.toCharArray(),0,str.length()); 462 } 463}