/*
* $Id: HtmlResponseWriter.java,v 1.47.4.13 2008/12/11 17:53:22 rlubke Exp $
*/
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can obtain
* a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
* or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [year]
* [name of copyright owner]"
*
* Contributor(s):
*
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*
* Portions Copyrighted 2013 Nuxeo
*/
package org.nuxeo.ecm.platform.ui.web.util;
import java.io.IOException;
import java.io.Writer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.faces.FacesException;
import javax.faces.component.UIComponent;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import com.sun.faces.RIConstants;
import com.sun.faces.config.WebConfiguration;
import com.sun.faces.io.FastStringWriter;
import com.sun.faces.renderkit.html_basic.HtmlResponseWriter;
import com.sun.faces.util.HtmlUtils;
import com.sun.faces.util.MessageUtils;
/**
* CSV specific response writer copied pasted from
* com.sun.faces.renderkit.html_basic.HtmlResponseWriter without the HTML
* encode part.
*
* @since 5.9.1, 5.8-HF01
*/
public class NXHtmlResponseWriter extends ResponseWriter {
// Content Type for this Writer.
//
private String contentType = "text/html";
// Character encoding of that Writer - this may be null
// if the encoding isn't known.
//
private String encoding = null;
// Writer to use for output;
//
private Writer writer = null;
// True when we need to close a start tag
//
private boolean closeStart;
// Configuration flag regarding disableUnicodeEscaping
//
private WebConfiguration.DisableUnicodeEscaping disableUnicodeEscaping;
// Flag to escape Unicode
//
private boolean escapeUnicode;
// Flag to escape ISO-8859-1 codes
//
private boolean escapeIso;
// flag to indicate we're writing a CDATA section
private boolean writingCdata;
// flat to indicate the current element is CDATA
private boolean isCdata;
// flag to indicate that we're writing a 'script' or 'style' element
private boolean isScript;
// flag to indicate that we're writing a 'style' element
private boolean isStyle;
// flag to indicate that we're writing a 'src' attribute as part of
// 'script' or 'style' element
private boolean scriptOrStyleSrc;
// flag to indicate if the content type is Xhtml
private boolean isXhtml;
// HtmlResponseWriter to use when buffering is required
private Writer origWriter;
// Keep one instance of the script buffer per Writer
private FastStringWriter scriptBuffer;
// Keep one instance of attributesBuffer to buffer the writting
// of all attributes for a particular element to reducr the number
// of writes
private FastStringWriter attributesBuffer;
// Enables hiding of inlined script and style
// elements from old browsers
private Boolean isScriptHidingEnabled;
// Enables scripts to be included in attribute values
private Boolean isScriptInAttributeValueEnabled;
// Internal buffer used when outputting properly escaped information
// using HtmlUtils class.
//
private char[] buffer = new char[1028];
// Internal buffer for to store the result of String.getChars() for
// values passed to the writer as String to reduce the overhead
// of String.charAt(). This buffer will be grown, if necessary, to
// accomodate larger values.
private char[] textBuffer = new char[128];
static final Pattern CDATA_START_SLASH_SLASH;
static final Pattern CDATA_END_SLASH_SLASH;
static final Pattern CDATA_START_SLASH_STAR;
static final Pattern CDATA_END_SLASH_STAR;
static {
// At the beginning of a line, match // followed by any amount of
// whitespace, followed by <![CDATA[
CDATA_START_SLASH_SLASH = Pattern.compile("^//\\s*\\Q<![CDATA[\\E");
// At the end of a line, match // followed by any amout of whitespace,
// followed by ]]>
CDATA_END_SLASH_SLASH = Pattern.compile("//\\s*\\Q]]>\\E$");
// At the beginning of a line, match /* followed by any amout of
// whitespace, followed by <![CDATA[, followed by any amount of
// whitespace,
// followed by */
CDATA_START_SLASH_STAR = Pattern.compile("^/\\*\\s*\\Q<![CDATA[\\E\\s*\\*/");
// At the end of a line, match /* followed by any amount of whitespace,
// followed by ]]> followed by any amount of whitespace, followed by */
CDATA_END_SLASH_STAR = Pattern.compile("/\\*\\s*\\Q]]>\\E\\s*\\*/$");
}
// ------------------------------------------------------------
// Constructors
/**
* Constructor sets the <code>ResponseWriter</code> and encoding, and
* enables script hiding by default.
*
* @param writer the <code>ResponseWriter</code>
* @param contentType the content type.
* @param encoding the character encoding.
* @throws javax.faces.FacesException the encoding is not recognized.
*/
public NXHtmlResponseWriter(Writer writer, String contentType,
String encoding) throws FacesException {
this(writer, contentType, encoding, null, null, null);
}
/**
* <p>
* Constructor sets the <code>ResponseWriter</code> and encoding.
* </p>
* <p>
* The argument configPrefs is a map of configurable prefs that affect this
* instance's behavior. Supported keys are:
* </p>
* <p>
* BooleanWebContextInitParameter.EnableJSStyleHiding: <code>true</code> if
* the writer should attempt to hide JS from older browsers
* </p>
*
* @param writer the <code>ResponseWriter</code>
* @param contentType the content type.
* @param encoding the character encoding.
* @throws javax.faces.FacesException the encoding is not recognized.
*/
public NXHtmlResponseWriter(Writer writer, String contentType,
String encoding, Boolean isScriptHidingEnabled,
Boolean isScriptInAttributeValueEnabled,
WebConfiguration.DisableUnicodeEscaping disableUnicodeEscaping)
throws FacesException {
this.writer = writer;
if (null != contentType) {
this.contentType = contentType;
}
this.encoding = encoding;
// init those configuration parameters not yet initialized
WebConfiguration webConfig = null;
if (isScriptHidingEnabled == null) {
webConfig = getWebConfiguration(webConfig);
isScriptHidingEnabled = (null == webConfig) ? WebConfiguration.BooleanWebContextInitParameter.EnableJSStyleHiding.getDefaultValue()
: webConfig.isOptionEnabled(WebConfiguration.BooleanWebContextInitParameter.EnableJSStyleHiding);
}
if (isScriptInAttributeValueEnabled == null) {
webConfig = getWebConfiguration(webConfig);
isScriptInAttributeValueEnabled = (null == webConfig) ? WebConfiguration.BooleanWebContextInitParameter.EnableScriptInAttributeValue.getDefaultValue()
: webConfig.isOptionEnabled(WebConfiguration.BooleanWebContextInitParameter.EnableScriptInAttributeValue);
}
if (disableUnicodeEscaping == null) {
webConfig = getWebConfiguration(webConfig);
disableUnicodeEscaping = WebConfiguration.DisableUnicodeEscaping.getByValue((null == webConfig) ? WebConfiguration.WebContextInitParameter.DisableUnicodeEscaping.getDefaultValue()
: webConfig.getOptionValue(WebConfiguration.WebContextInitParameter.DisableUnicodeEscaping));
if (disableUnicodeEscaping == null) {
disableUnicodeEscaping = WebConfiguration.DisableUnicodeEscaping.False;
}
}
// and store them for later use
this.isScriptHidingEnabled = isScriptHidingEnabled;
this.isScriptInAttributeValueEnabled = isScriptInAttributeValueEnabled;
this.disableUnicodeEscaping = disableUnicodeEscaping;
this.attributesBuffer = new FastStringWriter(128);
// Check the character encoding
if (!HtmlUtils.validateEncoding(encoding)) {
throw new IllegalArgumentException(
MessageUtils.getExceptionMessageString(MessageUtils.ENCODING_ERROR_MESSAGE_ID));
}
String charsetName = encoding.toUpperCase();
switch (disableUnicodeEscaping) {
case True:
// html escape noting (except the dangerous characters like "<>'"
// etc
escapeUnicode = false;
escapeIso = false;
break;
case False:
// html escape any non-ascii character
escapeUnicode = true;
escapeIso = true;
break;
case Auto:
// is stream capable of rendering unicode, do not escape
escapeUnicode = !HtmlUtils.isUTFencoding(charsetName);
// is stream capable of rendering unicode or iso-8859-1, do not
// escape
escapeIso = !HtmlUtils.isISO8859_1encoding(charsetName)
&& !HtmlUtils.isUTFencoding(charsetName);
break;
}
}
private WebConfiguration getWebConfiguration(WebConfiguration webConfig) {
if (webConfig != null) {
return webConfig;
}
FacesContext context = FacesContext.getCurrentInstance();
if (null != context) {
ExternalContext extContext = context.getExternalContext();
if (null != extContext) {
webConfig = WebConfiguration.getInstance(extContext);
}
}
return webConfig;
}
// -------------------------------------------------- Methods From
// Closeable
/** Methods From <code>java.io.Writer</code> */
public void close() throws IOException {
closeStartIfNecessary();
writer.close();
}
// -------------------------------------------------- Methods From
// Flushable
/**
* Flush any buffered output to the contained writer.
*
* @throws IOException if an input/output error occurs.
*/
public void flush() throws IOException {
// NOTE: Internal buffer's contents (the ivar "buffer") is
// written to the contained writer in the HtmlUtils class - see
// HtmlUtils.flushBuffer method; Buffering is done during
// writeAttribute/writeText - otherwise, output is written
// directly to the writer (ex: writer.write(....)..
//
// close any previously started element, if necessary
closeStartIfNecessary();
}
// ---------------------------------------------------------- Public
// Methods
/** @return the content type such as "text/html" for this ResponseWriter. */
public String getContentType() {
return contentType;
}
/**
* <p>
* Create a new instance of this <code>ResponseWriter</code> using a
* different <code>Writer</code>.
*
* @param writer The <code>Writer</code> that will be used to create
* another <code>ResponseWriter</code>.
*/
public ResponseWriter cloneWithWriter(Writer writer) {
try {
return new HtmlResponseWriter(writer, getContentType(),
getCharacterEncoding(), isScriptHidingEnabled,
isScriptInAttributeValueEnabled, disableUnicodeEscaping,
false);
} catch (FacesException e) {
// This should never happen
throw new IllegalStateException();
}
}
/** Output the text for the end of a document. */
public void endDocument() throws IOException {
writer.flush();
}
/**
* <p>
* Write the end of an element. This method will first close any open
* element created by a call to <code>startElement()</code>.
*
* @param name Name of the element to be ended
* @throws IOException if an input/output error occurs
* @throws NullPointerException if <code>name</code> is <code>null</code>
*/
public void endElement(String name) throws IOException {
if (name == null) {
throw new NullPointerException(
MessageUtils.getExceptionMessageString(
MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID,
"name"));
}
isXhtml = getContentType().equals(RIConstants.XHTML_CONTENT_TYPE);
if (isScriptOrStyle(name) && !scriptOrStyleSrc
&& writer instanceof FastStringWriter) {
String result = ((FastStringWriter) writer).getBuffer().toString();
writer = origWriter;
if (result != null) {
String trim = result.trim();
if (isXhtml) {
if (isScript) {
Matcher cdataStartSlashSlash = CDATA_START_SLASH_SLASH.matcher(trim), cdataEndSlashSlash = CDATA_END_SLASH_SLASH.matcher(trim), cdataStartSlashStar = CDATA_START_SLASH_STAR.matcher(trim), cdataEndSlashStar = CDATA_END_SLASH_STAR.matcher(trim);
int trimLen = trim.length(), start, end;
// case 1 start is // end is //
if (cdataStartSlashSlash.find()
&& cdataEndSlashSlash.find()) {
start = cdataStartSlashSlash.end()
- cdataStartSlashSlash.start();
end = trimLen
- (cdataEndSlashSlash.end() - cdataEndSlashSlash.start());
writer.write(trim.substring(start, end));
}
// case 2 start is // end is /* */
else if ((null != cdataStartSlashSlash.reset() && cdataStartSlashSlash.find())
&& cdataEndSlashStar.find()) {
start = cdataStartSlashSlash.end()
- cdataStartSlashSlash.start();
end = trimLen
- (cdataEndSlashStar.end() - cdataEndSlashStar.start());
writer.write(trim.substring(start, end));
}
// case 3 start is /* */ end is /* */
else if (cdataStartSlashStar.find()
&& (null != cdataEndSlashStar.reset() && cdataEndSlashStar.find())) {
start = cdataStartSlashStar.end()
- cdataStartSlashStar.start();
end = trimLen
- (cdataEndSlashStar.end() - cdataEndSlashStar.start());
writer.write(trim.substring(start, end));
}
// case 4 start is /* */ end is //
else if ((null != cdataStartSlashStar.reset() && cdataStartSlashStar.find())
&& (null != cdataEndSlashStar.reset() && cdataEndSlashSlash.find())) {
start = cdataStartSlashStar.end()
- cdataStartSlashStar.start();
end = trimLen
- (cdataEndSlashSlash.end() - cdataEndSlashSlash.start());
writer.write(trim.substring(start, end));
}
// case 5 no commented out cdata present.
else {
writer.write(result);
}
} else {
if (trim.startsWith("<![CDATA[")
&& trim.endsWith("]]>")) {
writer.write(trim.substring(9, trim.length() - 3));
} else {
writer.write(result);
}
}
} else {
if (trim.startsWith("<!--") && trim.endsWith("//-->")) {
writer.write(trim.substring(4, trim.length() - 5));
} else {
writer.write(result);
}
}
}
if (isXhtml) {
if (!writingCdata) {
if (isScript) {
writer.write("\n//]]>\n");
} else {
writer.write("\n]]>\n");
}
}
} else {
if (isScriptHidingEnabled) {
writer.write("\n//-->\n");
}
}
}
isScript = false;
isStyle = false;
if ("cdata".equalsIgnoreCase(name)) {
writer.write("]]>");
writingCdata = false;
isCdata = false;
return;
}
// See if we need to close the start of the last element
if (closeStart) {
boolean isEmptyElement = HtmlUtils.isEmptyElement(name);
// Tricky: we need to use the writer ivar here, rather than the
// one from the FacesContext because we don't want
// spurious /> characters to appear in the output.
if (isEmptyElement) {
flushAttributes();
writer.write(" />");
closeStart = false;
return;
}
flushAttributes();
writer.write('>');
closeStart = false;
}
writer.write("</");
writer.write(name);
writer.write('>');
}
/**
* @return the character encoding, such as "ISO-8859-1" for this
* ResponseWriter. Refer to: <a
* href="http://www.iana.org/assignments/character-sets"
* >theIANA</a> for a list of character encodings.
*/
public String getCharacterEncoding() {
return encoding;
}
/**
* <p>
* Write the text that should begin a response.
* </p>
*
* @throws IOException if an input/output error occurs
*/
public void startDocument() throws IOException {
// do nothing;
}
/**
* <p>
* Write the start of an element, up to and including the element name.
* Clients call <code>writeAttribute()</code> or
* <code>writeURIAttribute()</code> methods to add attributes after calling
* this method.
*
* @param name Name of the starting element
* @param componentForElement The UIComponent instance that applies to this
* element. This argument may be <code>null</code>.
* @throws IOException if an input/output error occurs
* @throws NullPointerException if <code>name</code> is <code>null</code>
*/
public void startElement(String name, UIComponent componentForElement)
throws IOException {
if (name == null) {
throw new NullPointerException(
MessageUtils.getExceptionMessageString(
MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID,
"name"));
}
closeStartIfNecessary();
isScriptOrStyle(name);
scriptOrStyleSrc = false;
if ("cdata".equalsIgnoreCase(name)) {
isCdata = true;
writingCdata = true;
writer.write("<![CDATA[");
closeStart = false;
return;
} else if (writingCdata) {
// starting an element within a cdata section,
// keep escaping disabled
isCdata = false;
writingCdata = true;
}
writer.write('<');
writer.write(name);
closeStart = true;
}
@Override
public void write(char[] cbuf) throws IOException {
closeStartIfNecessary();
writer.write(cbuf);
}
@Override
public void write(int c) throws IOException {
closeStartIfNecessary();
writer.write(c);
}
@Override
public void write(String str) throws IOException {
closeStartIfNecessary();
writer.write(str);
}
public void write(char[] cbuf, int off, int len) throws IOException {
closeStartIfNecessary();
writer.write(cbuf, off, len);
}
@Override
public void write(String str, int off, int len) throws IOException {
closeStartIfNecessary();
writer.write(str, off, len);
}
/**
* <p>
* Write a properly escaped attribute name and the corresponding value. The
* value text will be converted to a String if necessary. This method may
* only be called after a call to <code>startElement()</code>, and before
* the opened element has been closed.
* </p>
*
* @param name Attribute name to be added
* @param value Attribute value to be added
* @param componentPropertyName The name of the component property to which
* this attribute argument applies. This argument may be
* <code>null</code>.
* @throws IllegalStateException if this method is called when there is no
* currently open element
* @throws IOException if an input/output error occurs
* @throws NullPointerException if <code>name</code> is <code>null</code>
*/
public void writeAttribute(String name, Object value,
String componentPropertyName) throws IOException {
if (name == null) {
throw new NullPointerException(
MessageUtils.getExceptionMessageString(
MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID,
"name"));
}
if (value == null) {
return;
}
if (isCdata) {
return;
}
if (name.equalsIgnoreCase("src") && isScriptOrStyle()) {
scriptOrStyleSrc = true;
}
Class valueClass = value.getClass();
// Output Boolean values specially
if (valueClass == Boolean.class) {
if (Boolean.TRUE.equals(value)) {
// NOTE: HTML 4.01 states that boolean attributes
// may legally take a single value which is the
// name of the attribute itself or appear using
// minimization.
// http://www.w3.org/TR/html401/intro/sgmltut.html#h-3.3.4.2
attributesBuffer.write(' ');
attributesBuffer.write(name);
attributesBuffer.write("=\"");
attributesBuffer.write(name);
attributesBuffer.write('"');
}
} else {
attributesBuffer.write(' ');
attributesBuffer.write(name);
attributesBuffer.write("=\"");
// write the attribute value
String val = value.toString();
ensureTextBufferCapacity(val);
HtmlUtils.writeAttribute(attributesBuffer, escapeUnicode,
escapeIso, buffer, val, textBuffer,
isScriptInAttributeValueEnabled);
attributesBuffer.write('"');
}
}
/**
* <p>
* Write a comment string containing the specified text. The text will be
* converted to a String if necessary. If there is an open element that has
* been created by a call to <code>startElement()</code>, that element will
* be closed first.
* </p>
*
* @param comment Text content of the comment
* @throws IOException if an input/output error occurs
* @throws NullPointerException if <code>comment</code> is
* <code>null</code>
*/
public void writeComment(Object comment) throws IOException {
if (comment == null) {
throw new NullPointerException(
MessageUtils.getExceptionMessageString(MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID));
}
if (writingCdata) {
return;
}
closeStartIfNecessary();
// Don't include a trailing space after the '<!--'
// or a leading space before the '-->' to support
// IE conditional commentsoth
writer.write("<!--");
writer.write(comment.toString());
writer.write("-->");
}
/**
* <p>
* Write a properly escaped single character, If there is an open element
* that has been created by a call to <code>startElement()</code>, that
* element will be closed first.
* </p>
* <p/>
* <p>
* All angle bracket occurrences in the argument must be escaped using the
* > < syntax.
* </p>
*
* @param text Text to be written
* @throws IOException if an input/output error occurs
*/
public void writeText(char text) throws IOException {
closeStartIfNecessary();
writer.write(text);
}
/**
* <p>
* Write properly escaped text from a character array. The output from this
* command is identical to the invocation:
* <code>writeText(c, 0, c.length)</code>. If there is an open element that
* has been created by a call to <code>startElement()</code>, that element
* will be closed first.
* </p>
* </p>
* <p/>
* <p>
* All angle bracket occurrences in the argument must be escaped using the
* > < syntax.
* </p>
*
* @param text Text to be written
* @throws IOException if an input/output error occurs
* @throws NullPointerException if <code>text</code> is <code>null</code>
*/
public void writeText(char text[]) throws IOException {
if (text == null) {
throw new NullPointerException(
MessageUtils.getExceptionMessageString(
MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID,
"text"));
}
closeStartIfNecessary();
writer.write(text);
}
/**
* <p>
* Write a properly escaped object. The object will be converted to a
* String if necessary. If there is an open element that has been created
* by a call to <code>startElement()</code>, that element will be closed
* first.
* </p>
*
* @param text Text to be written
* @param componentPropertyName The name of the component property to which
* this text argument applies. This argument may be
* <code>null</code>.
* @throws IOException if an input/output error occurs
* @throws NullPointerException if <code>text</code> is <code>null</code>
*/
public void writeText(Object text, String componentPropertyName)
throws IOException {
if (text == null) {
throw new NullPointerException(
MessageUtils.getExceptionMessageString(
MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID,
"text"));
}
closeStartIfNecessary();
writer.write(text.toString());
}
/**
* <p>
* Write properly escaped text from a character array. If there is an open
* element that has been created by a call to <code>startElement()</code>,
* that element will be closed first.
* </p>
* <p/>
* <p>
* All angle bracket occurrences in the argument must be escaped using the
* > < syntax.
* </p>
*
* @param text Text to be written
* @param off Starting offset (zero-relative)
* @param len Number of characters to be written
* @throws IndexOutOfBoundsException if the calculated starting or ending
* position is outside the bounds of the character array
* @throws IOException if an input/output error occurs
* @throws NullPointerException if <code>text</code> is <code>null</code>
*/
public void writeText(char text[], int off, int len) throws IOException {
if (text == null) {
throw new NullPointerException(
MessageUtils.getExceptionMessageString(
MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID,
"text"));
}
if (off < 0 || off > text.length || len < 0 || len > text.length) {
throw new IndexOutOfBoundsException();
}
closeStartIfNecessary();
writer.write(text, off, len);
}
/**
* <p>
* Write a properly encoded URI attribute name and the corresponding value.
* The value text will be converted to a String if necessary). This method
* may only be called after a call to <code>startElement()</code>, and
* before the opened element has been closed.
* </p>
*
* @param name Attribute name to be added
* @param value Attribute value to be added
* @param componentPropertyName The name of the component property to which
* this attribute argument applies. This argument may be
* <code>null</code>.
* @throws IllegalStateException if this method is called when there is no
* currently open element
* @throws IOException if an input/output error occurs
* @throws NullPointerException if <code>name</code> or <code>value</code>
* is <code>null</code>
*/
public void writeURIAttribute(String name, Object value,
String componentPropertyName) throws IOException {
if (name == null) {
throw new NullPointerException(
MessageUtils.getExceptionMessageString(
MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID,
"name"));
}
if (value == null) {
throw new NullPointerException(
MessageUtils.getExceptionMessageString(
MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID,
"value"));
}
if (isCdata) {
return;
}
if (name.equalsIgnoreCase("src") && isScriptOrStyle()) {
scriptOrStyleSrc = true;
}
attributesBuffer.write(' ');
attributesBuffer.write(name);
attributesBuffer.write("=\"");
String stringValue = value.toString();
ensureTextBufferCapacity(stringValue);
// Javascript URLs should not be URL-encoded
if (stringValue.startsWith("javascript:")) {
HtmlUtils.writeAttribute(attributesBuffer, escapeUnicode,
escapeIso, buffer, stringValue, textBuffer,
isScriptInAttributeValueEnabled);
} else {
HtmlUtils.writeURL(attributesBuffer, stringValue, textBuffer,
encoding);
}
attributesBuffer.write('"');
}
// --------------------------------------------------------- Private
// Methods
private void ensureTextBufferCapacity(String source) {
int len = source.length();
if (textBuffer.length < len) {
textBuffer = new char[len * 2];
}
}
/**
* This method automatically closes a previous element (if not already
* closed).
*
* @throws IOException if an error occurs writing
*/
private void closeStartIfNecessary() throws IOException {
if (closeStart) {
flushAttributes();
writer.write('>');
closeStart = false;
if (isScriptOrStyle() && !scriptOrStyleSrc) {
isXhtml = getContentType().equals(
RIConstants.XHTML_CONTENT_TYPE);
if (isXhtml) {
if (!writingCdata) {
if (isScript) {
writer.write("\n//<![CDATA[\n");
} else {
writer.write("\n<![CDATA[\n");
}
}
} else {
if (isScriptHidingEnabled) {
writer.write("\n<!--\n");
}
}
origWriter = writer;
if (scriptBuffer == null) {
scriptBuffer = new FastStringWriter(1024);
}
scriptBuffer.reset();
writer = scriptBuffer;
isScript = false;
isStyle = false;
}
}
}
private void flushAttributes() throws IOException {
// a little complex, but the end result is, potentially, two
// fewer temp objects created per call.
StringBuilder b = attributesBuffer.getBuffer();
int totalLength = b.length();
if (totalLength != 0) {
int curIdx = 0;
while (curIdx < totalLength) {
if ((totalLength - curIdx) > buffer.length) {
int end = curIdx + buffer.length;
b.getChars(curIdx, end, buffer, 0);
writer.write(buffer);
curIdx += buffer.length;
} else {
int len = totalLength - curIdx;
b.getChars(curIdx, curIdx + len, buffer, 0);
writer.write(buffer, 0, len);
curIdx += len;
}
}
attributesBuffer.reset();
}
}
private boolean isScriptOrStyle(String name) {
if ("script".equalsIgnoreCase(name)) {
isScript = true;
} else if ("style".equalsIgnoreCase(name)) {
isStyle = true;
} else {
isScript = false;
isStyle = false;
}
return (isScript || isStyle);
}
private boolean isScriptOrStyle() {
return (isScript || isStyle);
}
}