View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.shared.renderkit.html;
20  
21  import java.io.IOException;
22  import java.io.Writer;
23  import java.nio.charset.Charset;
24  import java.util.logging.Level;
25  import java.util.logging.Logger;
26  
27  import javax.faces.FacesException;
28  import javax.faces.component.UIComponent;
29  import javax.faces.context.ResponseWriter;
30  
31  import org.apache.myfaces.shared.renderkit.ContentTypeUtils;
32  import org.apache.myfaces.shared.renderkit.RendererUtils;
33  import org.apache.myfaces.shared.renderkit.html.util.UnicodeEncoder;
34  import org.apache.myfaces.shared.util.CommentUtils;
35  import org.apache.myfaces.shared.util.StreamCharBuffer;
36  
37  /**
38   * @author Manfred Geiler (latest modification by $Author: lu4242 $)
39   * @author Anton Koinov
40   * @version $Revision: 1469145 $ $Date: 2013-04-17 21:57:16 -0500 (Wed, 17 Apr 2013) $
41   */
42  public class HtmlResponseWriterImpl
43          extends ResponseWriter
44  {
45      //private static final Log log = LogFactory.getLog(HtmlResponseWriterImpl.class);
46      private static final Logger log = Logger.getLogger(HtmlResponseWriterImpl.class.getName());
47  
48      private static final String DEFAULT_CONTENT_TYPE = "text/html";
49      private static final String DEFAULT_CHARACTER_ENCODING = "ISO-8859-1";
50      private static final String UTF8 = "UTF-8";
51  
52      private static final String APPLICATION_XML_CONTENT_TYPE = "application/xml";
53      private static final String TEXT_XML_CONTENT_TYPE = "text/xml";
54      
55      //private boolean _writeDummyForm = false;
56      //private Set _dummyFormParams = null;
57  
58      /**
59       * The writer used as output, or in other words, the one passed on the constructor
60       */
61      private Writer _outputWriter;
62      
63      /**
64       * The writer we are using to store data.
65       */
66      private Writer _currentWriter;
67      
68      /**
69       * The writer used to buffer script and style content
70       */
71      private StreamCharBuffer _buffer;
72      
73      private String _contentType;
74      
75      private String _writerContentTypeMode;
76      
77      /**
78       * This var prevents check if the contentType is for xhtml multiple times.
79       */
80      private boolean _isXhtmlContentType;
81      
82      /**
83       * Indicate the current response writer should not close automatically html elements
84       * and let the writer close them.
85       */
86      private boolean _useStraightXml;
87      
88      private String _characterEncoding;
89      private boolean _wrapScriptContentWithXmlCommentTag;
90      private boolean _isUTF8;
91      
92      private String _startElementName;
93      private Boolean _isInsideScript;
94      private Boolean _isStyle;
95      private Boolean _isTextArea;
96      private UIComponent _startElementUIComponent;
97      private boolean _startTagOpen;
98  
99      private boolean _cdataOpen;
100 
101     private static final String CDATA_START = "<![CDATA[ \n";
102     private static final String CDATA_START_NO_LINE_RETURN = "<![CDATA[";
103     private static final String COMMENT_START = "<!--\n";
104     private static final String CDATA_COMMENT_START = "//<![CDATA[ \n";
105     private static final String CDATA_COMMENT_END = "\n//]]>";
106     private static final String CDATA_END = "\n]]>";
107     private static final String CDATA_END_NO_LINE_RETURN = "]]>";
108     private static final String COMMENT_COMMENT_END = "\n//-->";
109     private static final String COMMENT_END = "\n-->";
110 
111     static private final String[][] EMPTY_ELEMENT_ARR = new String[256][];
112 
113     static private final String[] A_NAMES = new String[]
114     {
115       "area",
116     };
117 
118     static private final String[] B_NAMES = new String[]
119     {
120       "br",
121       "base",
122       "basefont",
123     };
124 
125     static private final String[] C_NAMES = new String[]
126     {
127       "col",
128     };
129 
130     static private final String[] E_NAMES = new String[]
131     {
132       "embed",
133     };
134 
135     static private final String[] F_NAMES = new String[]
136     {
137       "frame",
138     };
139 
140     static private final String[] H_NAMES = new String[]
141     {
142       "hr",
143     };
144 
145     static private final String[] I_NAMES = new String[]
146     {
147       "img",
148       "input",
149       "isindex",
150     };
151 
152     static private final String[] L_NAMES = new String[]
153     {
154       "link",
155     };
156 
157     static private final String[] M_NAMES = new String[]
158     {
159       "meta",
160     };
161 
162     static private final String[] P_NAMES = new String[]
163     {
164       "param",
165     };
166 
167     static
168     {
169       EMPTY_ELEMENT_ARR['a'] = A_NAMES;
170       EMPTY_ELEMENT_ARR['A'] = A_NAMES;
171       EMPTY_ELEMENT_ARR['b'] = B_NAMES;
172       EMPTY_ELEMENT_ARR['B'] = B_NAMES;
173       EMPTY_ELEMENT_ARR['c'] = C_NAMES;
174       EMPTY_ELEMENT_ARR['C'] = C_NAMES;
175       EMPTY_ELEMENT_ARR['e'] = E_NAMES;
176       EMPTY_ELEMENT_ARR['E'] = E_NAMES;
177       EMPTY_ELEMENT_ARR['f'] = F_NAMES;
178       EMPTY_ELEMENT_ARR['F'] = F_NAMES;
179       EMPTY_ELEMENT_ARR['h'] = H_NAMES;
180       EMPTY_ELEMENT_ARR['H'] = H_NAMES;
181       EMPTY_ELEMENT_ARR['i'] = I_NAMES;
182       EMPTY_ELEMENT_ARR['I'] = I_NAMES;
183       EMPTY_ELEMENT_ARR['l'] = L_NAMES;
184       EMPTY_ELEMENT_ARR['L'] = L_NAMES;
185       EMPTY_ELEMENT_ARR['m'] = M_NAMES;
186       EMPTY_ELEMENT_ARR['M'] = M_NAMES;
187       EMPTY_ELEMENT_ARR['p'] = P_NAMES;
188       EMPTY_ELEMENT_ARR['P'] = P_NAMES;
189     }    
190     
191     public HtmlResponseWriterImpl(Writer writer, String contentType, String characterEncoding)
192     {
193         this(writer,contentType,characterEncoding,true);
194     }
195 
196     public HtmlResponseWriterImpl(Writer writer, String contentType, String characterEncoding,
197             boolean wrapScriptContentWithXmlCommentTag)
198     {
199         this(writer,contentType, characterEncoding, wrapScriptContentWithXmlCommentTag, 
200                 contentType != null && HtmlRendererUtils.isXHTMLContentType(contentType) ? 
201                     ContentTypeUtils.XHTML_CONTENT_TYPE : ContentTypeUtils.HTML_CONTENT_TYPE);
202     }
203     
204     public HtmlResponseWriterImpl(Writer writer, String contentType, String characterEncoding,
205              boolean wrapScriptContentWithXmlCommentTag, String writerContentTypeMode)
206     throws FacesException
207     {
208         _outputWriter = writer;
209         //The current writer to be used is the one used as output 
210         _currentWriter = _outputWriter;
211         _wrapScriptContentWithXmlCommentTag = wrapScriptContentWithXmlCommentTag;
212         
213         _contentType = contentType;
214         if (_contentType == null)
215         {
216             if (log.isLoggable(Level.FINE))
217             {
218                 log.fine("No content type given, using default content type " + DEFAULT_CONTENT_TYPE);
219             }
220             _contentType = DEFAULT_CONTENT_TYPE;
221         }
222         _writerContentTypeMode = writerContentTypeMode;
223         _isXhtmlContentType = writerContentTypeMode.indexOf(ContentTypeUtils.XHTML_CONTENT_TYPE) != -1;
224         
225         _useStraightXml = _isXhtmlContentType && (_contentType.indexOf(APPLICATION_XML_CONTENT_TYPE) != -1 ||
226                           _contentType.indexOf(TEXT_XML_CONTENT_TYPE) != -1);
227 
228         if (characterEncoding == null)
229         {
230             if (log.isLoggable(Level.FINE))
231             {
232                 log.fine("No character encoding given, using default character encoding " +
233                         DEFAULT_CHARACTER_ENCODING);
234             }
235             _characterEncoding = DEFAULT_CHARACTER_ENCODING;
236         }
237         else
238         {
239             // canonize to uppercase, that's the standard format
240             _characterEncoding = characterEncoding.toUpperCase();
241             
242             // Check if encoding is valid by javadoc of RenderKit.createResponseWriter()
243             if (!Charset.isSupported(_characterEncoding))
244             {
245                 throw new IllegalArgumentException("Encoding "+_characterEncoding
246                         +" not supported by HtmlResponseWriterImpl");
247             }
248         }
249         _isUTF8 = UTF8.equals(_characterEncoding);
250     }
251 
252     public static boolean supportsContentType(String contentType)
253     {
254         String[] supportedContentTypes = HtmlRendererUtils.getSupportedContentTypes();
255 
256         for (int i = 0; i < supportedContentTypes.length; i++)
257         {
258             String supportedContentType = supportedContentTypes[i];
259 
260             if(supportedContentType.indexOf(contentType)!=-1)
261             {
262                 return true;
263             }
264         }
265         return false;
266     }
267 
268     public String getContentType()
269     {
270         return _contentType;
271     }
272     
273     public String getWriterContentTypeMode()
274     {
275         return _writerContentTypeMode;
276     }
277 
278     public String getCharacterEncoding()
279     {
280         return _characterEncoding;
281     }
282 
283     public void flush() throws IOException
284     {
285         // API doc says we should not flush the underlying writer
286         //_writer.flush();
287         // but rather clear any values buffered by this ResponseWriter:
288         closeStartTagIfNecessary();
289     }
290 
291     public void startDocument()
292     {
293         // do nothing
294     }
295 
296     public void endDocument() throws IOException
297     {
298         _currentWriter.flush();
299     }
300 
301     public void startElement(String name, UIComponent uiComponent) throws IOException
302     {
303         if (name == null)
304         {
305             throw new NullPointerException("elementName name must not be null");
306         }
307 
308         closeStartTagIfNecessary();
309         _currentWriter.write('<');
310         _currentWriter.write(name);
311 
312         resetStartedElement();
313 
314         _startElementName = name;
315         _startElementUIComponent = uiComponent;
316         _startTagOpen = true;
317         
318         // Each time we start a element, it is necessary to check <script> or <style>,
319         // because we need to buffer all content to post process it later when it reach its end
320         // according to the initialization properties used.
321         if(isScript(name))
322         {
323             // handle a <script> start
324             _isInsideScript = Boolean.TRUE;
325             _isStyle = Boolean.FALSE;
326             _isTextArea = Boolean.FALSE;
327         }
328         else if (isStyle(name))
329         {
330             _isInsideScript = Boolean.FALSE;
331             _isStyle = Boolean.TRUE;
332             _isTextArea = Boolean.FALSE;
333         }
334     }
335 
336     @Override
337     public void startCDATA() throws IOException
338     {
339         if (!_cdataOpen)
340         {
341             write(CDATA_START_NO_LINE_RETURN);
342             _cdataOpen = true;
343         }
344     }
345 
346     @Override
347     public void endCDATA() throws IOException
348     {
349         if (_cdataOpen)
350         {
351             write(CDATA_END_NO_LINE_RETURN);
352             _cdataOpen = false;
353         }
354     }
355 
356     private void closeStartTagIfNecessary() throws IOException
357     {
358         if (_startTagOpen)
359         {
360             if (!_useStraightXml && isEmptyElement(_startElementName))
361             {
362                 _currentWriter.write(" />");
363                 // make null, this will cause NullPointer in some invalid element nestings
364                 // (better than doing nothing)
365                 resetStartedElement();
366             }
367             else
368             {
369                 _currentWriter.write('>');
370 
371                 /*
372                 if(isScript(_startElementName))
373                 {
374                     if(HtmlRendererUtils.isXHTMLContentType(_contentType))
375                     {
376                         if(HtmlRendererUtils.isAllowedCdataSection(FacesContext.getCurrentInstance()))
377                         {
378                             _currentWriter.write(CDATA_START);
379                         }
380                     }
381                     else
382                     {
383                         _currentWriter.write(COMMENT_START);
384                     }
385                 }*/
386                 if (isScript(_startElementName) && (_isXhtmlContentType || _wrapScriptContentWithXmlCommentTag))
387                 {
388                     //_bufferedWriter.reset();
389                     //_currentWriter = _bufferedWriter;
390                     getInternalBuffer(true);
391                     _currentWriter = getInternalBuffer().getWriter();
392                 }                
393                 if (isStyle(_startElementName) && _isXhtmlContentType)
394                 {
395                     //_bufferedWriter.reset();
396                     //_currentWriter = _bufferedWriter;
397                     getInternalBuffer(true);
398                     _currentWriter = getInternalBuffer().getWriter();
399                 }
400             }
401             _startTagOpen = false;
402         }
403     }
404     
405     private boolean isEmptyElement(String elem)
406     {
407         // Code taken from trinidad
408         // =-=AEW Performance?  Certainly slower to use a hashtable,
409         // at least if we can't assume the input name is lowercased.
410         // -= Leonardo Uribe =- elem.toLowerCase() internally creates an array,
411         // and the contains() force a call to hashCode(). The array uses simple
412         // char comparison, which at the end is faster and use less memory.
413         // Note this call is very frequent, so at the end it is worth to do it.
414         String[] array = EMPTY_ELEMENT_ARR[elem.charAt(0)];
415         if (array != null)
416         {
417             for (int i = array.length - 1; i >= 0; i--)
418             {
419                 if (elem.equalsIgnoreCase(array[i]))
420                 {
421                     return true;
422                 }
423             }
424         }
425         return false;
426     }
427 
428     private void resetStartedElement()
429     {
430         _startElementName = null;
431         _startElementUIComponent = null;
432         _isStyle = null;
433         _isTextArea = null;
434     }
435 
436     public void endElement(String name) throws IOException
437     {
438         if (name == null)
439         {
440             throw new NullPointerException("elementName name must not be null");
441         }
442 
443         if (log.isLoggable(Level.WARNING))
444         {
445             if (_startElementName != null &&
446                 !name.equals(_startElementName))
447             {
448                 log.warning("HTML nesting warning on closing " + name + ": element " + _startElementName +
449                         (_startElementUIComponent==null?"":(" rendered by component : "+
450                         RendererUtils.getPathToComponent(_startElementUIComponent)))+" not explicitly closed");
451             }
452         }
453 
454         if(_startTagOpen)
455         {
456 
457             // we will get here only if no text or attribute was written after the start element was opened
458             // now we close out the started tag - if it is an empty tag, this is then fully closed
459             closeStartTagIfNecessary();
460 
461             //tag was no empty tag - it has no accompanying end tag now.
462             if(_startElementName!=null)
463             {
464                 if (isScript() && (_isXhtmlContentType || _wrapScriptContentWithXmlCommentTag))
465                 {
466                     writeScriptContent();
467                     _currentWriter = _outputWriter;
468                 }
469                 else if (isStyle() && _isXhtmlContentType)
470                 {
471                     writeStyleContent();
472                     _currentWriter = _outputWriter;
473                 }
474 
475                 //write closing tag
476                 writeEndTag(name);
477             }
478         }
479         else
480         {
481             if (!_useStraightXml && isEmptyElement(name))
482             {
483            /*
484            Should this be here?  It warns even when you have an x:htmlTag value="br", it should just close.
485 
486                 if (log.isWarnEnabled())
487                     log.warn("HTML nesting warning on closing " + name + 
488                         ": This element must not contain nested elements or text in HTML");
489                     */
490             }
491             else
492             {
493                 if (isScript() && (_isXhtmlContentType || _wrapScriptContentWithXmlCommentTag))
494                 {
495                     writeScriptContent();
496                     _currentWriter = _outputWriter;
497                 }
498                 else if (isStyle() && _isXhtmlContentType)
499                 {
500                     writeStyleContent();
501                     _currentWriter = _outputWriter;
502                 }
503                 writeEndTag(name);
504             }
505         }
506 
507         resetStartedElement();
508     }
509 
510 
511     
512     private void writeStyleContent() throws IOException
513     {
514         String content = getInternalBuffer().toString();
515         
516         if(_isXhtmlContentType)
517         {
518             // In xhtml, the content inside <style> tag is PCDATA, but
519             // in html the content is CDATA, so in order to preserve 
520             // compatibility we need to wrap the content inside proper
521             // CDATA tags.
522             // Since the response content type is xhtml, we can use
523             // simple CDATA without comments, but note we need to check
524             // when we are using any valid notation (simple CDATA, commented CDATA, xml comment) 
525             String trimmedContent = content.trim();
526             if (trimmedContent.startsWith(CommentUtils.CDATA_SIMPLE_START) && trimmedContent.endsWith(
527                     CommentUtils.CDATA_SIMPLE_END))
528             {
529                 _outputWriter.write(content);
530                 return;
531             }
532             else if (CommentUtils.isStartMatchWithCommentedCDATA(trimmedContent) && 
533                     CommentUtils.isEndMatchWithCommentedCDATA(trimmedContent))
534             {
535                 _outputWriter.write(content);
536                 return;
537             }
538             else if (trimmedContent.startsWith(CommentUtils.COMMENT_SIMPLE_START) && 
539                     trimmedContent.endsWith(CommentUtils.COMMENT_SIMPLE_END))
540             {
541                 //Use comment wrap is valid, but for xhtml it is preferred to use CDATA
542                 _outputWriter.write(CDATA_START);
543                 _outputWriter.write(trimmedContent.substring(4,trimmedContent.length()-3));
544                 _outputWriter.write(CDATA_END);
545                 return;
546             }
547             else
548             {
549                 _outputWriter.write(CDATA_START);
550                 _outputWriter.write(content);
551                 _outputWriter.write(CDATA_END);
552                 return;                
553             }
554         }
555         // If the response is handled as text/html, 
556         // it is not necessary to wrap with xml comment tag,
557         // so we can just write the content as is.
558         _outputWriter.write(content);
559     }
560     
561     private void writeScriptContent() throws IOException
562     {
563         String content = getInternalBuffer().toString();
564         String trimmedContent = null;
565         
566         if(_isXhtmlContentType)
567         {
568             trimmedContent = content.trim();
569             
570             if ( trimmedContent.startsWith(CommentUtils.COMMENT_SIMPLE_START) && 
571                     CommentUtils.isEndMatchtWithInlineCommentedXmlCommentTag(trimmedContent))
572             {
573                 // In xhtml use xml comment to wrap is invalid, so it is only required to remove the <!--
574                 // the ending //--> will be parsed as a comment, so it will not be a problem. Let it on the content.
575                 if (_cdataOpen)
576                 {
577                     _outputWriter.write("//\n");
578                 }
579                 else
580                 {
581                    _outputWriter.write(CDATA_COMMENT_START);
582                 }
583 
584                 _outputWriter.write(trimmedContent.substring(4));
585 
586                 if (_cdataOpen)
587                 {
588                     _outputWriter.write("\n");
589                 }
590                 else
591                 {
592                     _outputWriter.write(CDATA_COMMENT_END);
593                 }
594                 
595                 return;
596             }
597             else if (CommentUtils.isStartMatchWithCommentedCDATA(trimmedContent) && 
598                     CommentUtils.isEndMatchWithCommentedCDATA(trimmedContent))
599             {
600                 _outputWriter.write(content);
601                 return;
602             }
603             else if (CommentUtils.isStartMatchWithInlineCommentedCDATA(trimmedContent) && 
604                     CommentUtils.isEndMatchWithInlineCommentedCDATA(trimmedContent))
605             {
606                 _outputWriter.write(content);
607                 return;
608             }
609             else
610             {
611                 // <script> in xhtml has as content type PCDATA, but in html it is CDATA,
612                 // so we need to wrap here to prevent problems.
613                 if (_cdataOpen)
614                 {
615                     _outputWriter.write("//\n");
616                 }
617                 else
618                 {
619                    _outputWriter.write(CDATA_COMMENT_START);
620                 }
621 
622                 _outputWriter.write(content);
623 
624                 if (_cdataOpen)
625                 {
626                     _outputWriter.write("\n");
627                 }
628                 else
629                 {
630                     _outputWriter.write(CDATA_COMMENT_END);
631                 }
632 
633                 return;
634             }
635         }
636         else
637         {
638             if (_wrapScriptContentWithXmlCommentTag)
639             {
640                 trimmedContent = content.trim();
641                 
642                 if ( trimmedContent.startsWith(CommentUtils.COMMENT_SIMPLE_START) && 
643                         CommentUtils.isEndMatchtWithInlineCommentedXmlCommentTag(trimmedContent))
644                 {
645                     _outputWriter.write(content);
646                     return;
647                 }
648                 else if (CommentUtils.isStartMatchWithCommentedCDATA(trimmedContent) && 
649                         CommentUtils.isEndMatchWithCommentedCDATA(trimmedContent))
650                 {
651                     _outputWriter.write(content);
652                     return;
653                 }
654                 else if (CommentUtils.isStartMatchWithInlineCommentedCDATA(trimmedContent) && 
655                         CommentUtils.isEndMatchWithInlineCommentedCDATA(trimmedContent))
656                 {
657                     _outputWriter.write(content);
658                     return;
659                 }
660                 else
661                 {
662                     _outputWriter.write(COMMENT_START);
663                     _outputWriter.write(content);
664                     _outputWriter.write(COMMENT_COMMENT_END);
665                     return;
666                 }
667             }
668         }
669         
670         //If no return, just write everything
671         _outputWriter.write(content);
672     }
673     
674 
675     private void writeEndTag(String name)
676         throws IOException
677     {
678         /*
679         if(isScript(name)) 
680         {
681             if(HtmlRendererUtils.isXHTMLContentType(_contentType))
682             {
683                 if(HtmlRendererUtils.isAllowedCdataSection(FacesContext.getCurrentInstance()))
684                 {
685                     _currentWriter.write(CDATA_COMMENT_END);
686                 }
687             }
688             else
689             {
690                 _currentWriter.write(COMMENT_COMMENT_END);
691             }
692             
693             // reset _isInsideScript
694             _isInsideScript = Boolean.FALSE;
695         }*/
696         
697         if (isScript(name))
698         {
699             // reset _isInsideScript
700             _isInsideScript = Boolean.FALSE;
701         }
702         else if (isStyle(name))
703         {
704             _isStyle = Boolean.FALSE;
705         }
706 
707         _currentWriter.write("</");
708         _currentWriter.write(name);
709         _currentWriter.write('>');
710     }
711 
712     public void writeAttribute(String name, Object value, String componentPropertyName) throws IOException
713     {
714         if (name == null)
715         {
716             throw new NullPointerException("attributeName name must not be null");
717         }
718         if (!_startTagOpen)
719         {
720             throw new IllegalStateException("Must be called before the start element is closed (attribute '"
721                     + name + "')");
722         }
723 
724         if (value instanceof Boolean)
725         {
726             if (((Boolean)value).booleanValue())
727             {
728                 // name as value for XHTML compatibility
729                 _currentWriter.write(' ');
730                 _currentWriter.write(name);
731                 _currentWriter.write("=\"");
732                 _currentWriter.write(name);
733                 _currentWriter.write('"');
734             }
735         }
736         else
737         {
738             String strValue = (value==null)?"":value.toString();
739             _currentWriter.write(' ');
740             _currentWriter.write(name);
741             _currentWriter.write("=\"");
742             org.apache.myfaces.shared.renderkit.html.util.HTMLEncoder.encode(_currentWriter,
743                     strValue, false, false, !_isUTF8);
744             _currentWriter.write('"');
745         }
746     }
747 
748     public void writeURIAttribute(String name, Object value, String componentPropertyName) throws IOException
749     {
750         if (name == null)
751         {
752             throw new NullPointerException("attributeName name must not be null");
753         }
754         if (!_startTagOpen)
755         {
756             throw new IllegalStateException("Must be called before the start element is closed (attribute '"
757                     + name + "')");
758         }
759 
760         String strValue = value.toString();
761         _currentWriter.write(' ');
762         _currentWriter.write(name);
763         _currentWriter.write("=\"");
764         if (strValue.toLowerCase().startsWith("javascript:"))
765         {
766             org.apache.myfaces.shared.renderkit.html.util.HTMLEncoder.encode(_currentWriter,
767                     strValue, false, false, !_isUTF8);
768         }
769         else
770         {
771             /*
772             Todo: what is this section about? still needed?
773             client side state saving is now done via javascript...
774 
775             if (_startElementName.equalsIgnoreCase(HTML.ANCHOR_ELEM) && //Also support image and button urls?
776                 name.equalsIgnoreCase(HTML.HREF_ATTR) &&
777                 !strValue.startsWith("#"))
778             {
779                 FacesContext facesContext = FacesContext.getCurrentInstance();
780                 if (facesContext.getApplication().getStateManager().isSavingStateInClient(facesContext))
781                 {
782                     // saving state in url depends on the work together
783                     // of 3 (theoretically) pluggable components:
784                     // ViewHandler, ResponseWriter and ViewTag
785                     // We should try to make this HtmlResponseWriterImpl able
786                     // to handle this alone!
787                     if (strValue.indexOf('?') < 0)
788                     {
789                         strValue = strValue + '?' + JspViewHandlerImpl.URL_STATE_MARKER;
790                     }
791                     else
792                     {
793                         strValue = strValue + '&' + JspViewHandlerImpl.URL_STATE_MARKER;
794                     }
795                 }
796             }
797             */
798             //_writer.write(strValue);
799             org.apache.myfaces.shared.renderkit.html.util.HTMLEncoder.encodeURIAtributte(_currentWriter,
800                             strValue, _characterEncoding);
801         }
802         _currentWriter.write('"');
803     }
804 
805     public void writeComment(Object value) throws IOException
806     {
807         if (value == null)
808         {
809             throw new NullPointerException("comment name must not be null");
810         }
811 
812         closeStartTagIfNecessary();
813         _currentWriter.write("<!--");
814         _currentWriter.write(value.toString());    //TODO: Escaping: must not have "-->" inside!
815         _currentWriter.write("-->");
816     }
817 
818     public void writeText(Object value, String componentPropertyName) throws IOException
819     {
820         if (value == null)
821         {
822             throw new NullPointerException("Text must not be null.");
823         }
824 
825         closeStartTagIfNecessary();
826 
827         String strValue = value.toString();
828 
829         if (isScriptOrStyle())
830         {
831             // Don't bother encoding anything if chosen character encoding is UTF-8
832             if (_isUTF8)
833             {
834                 _currentWriter.write(strValue);
835             }
836             else
837             {
838                 UnicodeEncoder.encode(_currentWriter, strValue);
839             }
840         }
841         else
842         {
843             org.apache.myfaces.shared.renderkit.html.util.HTMLEncoder.encode(_currentWriter,
844                       strValue, false, false, !_isUTF8);
845         }
846     }
847 
848     public void writeText(char cbuf[], int off, int len) throws IOException
849     {
850         if (cbuf == null)
851         {
852             throw new NullPointerException("cbuf name must not be null");
853         }
854         if (cbuf.length < off + len)
855         {
856             throw new IndexOutOfBoundsException((off + len) + " > " + cbuf.length);
857         }
858 
859         closeStartTagIfNecessary();
860 
861         if (isScriptOrStyle())
862         {
863             String strValue = new String(cbuf, off, len);
864             // Don't bother encoding anything if chosen character encoding is UTF-8
865             if (_isUTF8)
866             {
867                 _currentWriter.write(strValue);
868             }
869             else
870             {
871                 UnicodeEncoder.encode(_currentWriter, strValue);
872             }
873         }
874         else if (isTextarea())
875         {
876             // For textareas we must *not* map successive spaces to &nbsp or Newlines to <br/>
877             org.apache.myfaces.shared.renderkit.html.util.HTMLEncoder.encode(
878                     cbuf, off, len, false, false, !_isUTF8, _currentWriter);
879         }
880         else
881         {
882             // We map successive spaces to &nbsp; and Newlines to <br/>
883             org.apache.myfaces.shared.renderkit.html.util.HTMLEncoder.encode(
884                     cbuf, off, len, true, true, !_isUTF8, _currentWriter);
885         }
886     }
887 
888     private boolean isScriptOrStyle()
889     {
890         //initializeStartedTagInfo();
891 
892         return (_isStyle != null && _isStyle.booleanValue()) ||
893                 (_isInsideScript != null && _isInsideScript.booleanValue());
894     }
895     
896     /**
897      * Is the given element a script tag?
898      * @param element
899      * @return
900      */
901     private boolean isScript(String element)
902     {
903         return (HTML.SCRIPT_ELEM.equalsIgnoreCase(element));
904     }
905     
906     private boolean isScript()
907     {
908         return (_isInsideScript != null && _isInsideScript.booleanValue());
909     }
910     
911     private boolean isStyle(String element)
912     {
913         return (HTML.STYLE_ELEM.equalsIgnoreCase(element));
914     }
915     
916     private boolean isStyle()
917     {
918         return (_isStyle != null && _isStyle.booleanValue());
919     }
920 
921     private boolean isTextarea()
922     {
923         initializeStartedTagInfo();
924 
925         return _isTextArea != null && _isTextArea.booleanValue();
926     }
927 
928     private void initializeStartedTagInfo()
929     {
930         if(_startElementName != null)
931         {
932             /*
933             if(_isStyle == null)
934             {
935                 if(_startElementName.equalsIgnoreCase(org.apache.myfaces.shared.renderkit.html.HTML.STYLE_ELEM))
936                 {
937                     _isStyle = Boolean.TRUE;
938                     _isTextArea = Boolean.FALSE;
939                 }
940                 else
941                 {
942                     _isStyle = Boolean.FALSE;
943                 }
944             }*/
945 
946             if(_isTextArea == null)
947             {
948                 if(_startElementName.equalsIgnoreCase(HTML.TEXTAREA_ELEM))
949                 {
950                     _isTextArea = Boolean.TRUE;
951                 }
952                 else
953                 {
954                     _isTextArea = Boolean.FALSE;
955                 }
956             }
957         }
958     }
959 
960     public ResponseWriter cloneWithWriter(Writer writer)
961     {
962         HtmlResponseWriterImpl newWriter
963                 = new HtmlResponseWriterImpl(writer, getContentType(), getCharacterEncoding(), 
964                         _wrapScriptContentWithXmlCommentTag, _writerContentTypeMode);
965         //newWriter._writeDummyForm = _writeDummyForm;
966         //newWriter._dummyFormParams = _dummyFormParams;
967         return newWriter;
968     }
969 
970 
971     // Writer methods
972 
973     public void close() throws IOException
974     {
975         closeStartTagIfNecessary();
976         _currentWriter.close();
977     }
978 
979     public void write(char cbuf[], int off, int len) throws IOException
980     {
981         closeStartTagIfNecessary();
982         // Don't bother encoding anything if chosen character encoding is UTF-8
983         if (_isUTF8)
984         {
985             _currentWriter.write(cbuf, off, len);
986         }
987         else
988         {
989             UnicodeEncoder.encode(_currentWriter, cbuf, off, len);
990         }
991     }
992 
993     public void write(int c) throws IOException
994     {
995         closeStartTagIfNecessary();
996         _currentWriter.write(c);
997     }
998 
999     public void write(char cbuf[]) throws IOException
1000     {
1001         closeStartTagIfNecessary();
1002         // Don't bother encoding anything if chosen character encoding is UTF-8
1003         if (_isUTF8)
1004         {
1005             _currentWriter.write(cbuf);
1006         }
1007         else
1008         {
1009             UnicodeEncoder.encode(_currentWriter, cbuf, 0, cbuf.length);
1010         }
1011     }
1012 
1013     public void write(String str) throws IOException
1014     {
1015         closeStartTagIfNecessary();
1016         // empty string commonly used to force the start tag to be closed.
1017         // in such case, do not call down the writer chain
1018         if (str != null && str.length() > 0)
1019         {
1020             // Don't bother encoding anything if chosen character encoding is UTF-8
1021             if (_isUTF8)
1022             {
1023                 _currentWriter.write(str);
1024             }
1025             else
1026             {
1027                 UnicodeEncoder.encode(_currentWriter, str);
1028             }
1029         }
1030     }
1031 
1032     public void write(String str, int off, int len) throws IOException
1033     {
1034         closeStartTagIfNecessary();
1035         // Don't bother encoding anything if chosen character encoding is UTF-8
1036         if (_isUTF8)
1037         {
1038             _currentWriter.write(str, off, len);
1039         }
1040         else
1041         {
1042             UnicodeEncoder.encode(_currentWriter, str, off, len);
1043         }
1044     }
1045     
1046     /**
1047      * This method ignores the <code>UIComponent</code> provided and simply calls
1048      * <code>writeText(Object,String)</code>
1049      * @since 1.2
1050      */
1051     public void writeText(Object object, UIComponent component, String string) throws IOException
1052     {
1053         writeText(object,string);
1054     }
1055     
1056     protected StreamCharBuffer getInternalBuffer()
1057     {
1058         return getInternalBuffer(false);
1059     }
1060     
1061     protected StreamCharBuffer getInternalBuffer(boolean reset)
1062     {
1063         if (_buffer == null)
1064         {
1065             _buffer = new StreamCharBuffer(256, 100);
1066         }
1067         else if (reset)
1068         {
1069             _buffer.reset();
1070         }
1071         return _buffer;
1072     }
1073 }