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