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 &lt;CODE&gt; &lt;PRE&gt; and &lt;TEXTAREA&gt;
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}