001/* 002 * $Id: HtmlResponseWriter.java,v 1.47.4.13 2008/12/11 17:53:22 rlubke Exp $ 003 */ 004 005/* 006 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 007 * 008 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved. 009 * 010 * The contents of this file are subject to the terms of either the GNU 011 * General Public License Version 2 only ("GPL") or the Common Development 012 * and Distribution License("CDDL") (collectively, the "License"). You 013 * may not use this file except in compliance with the License. You can obtain 014 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html 015 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific 016 * language governing permissions and limitations under the License. 017 * 018 * When distributing the software, include this License Header Notice in each 019 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt. 020 * Sun designates this particular file as subject to the "Classpath" exception 021 * as provided by Sun in the GPL Version 2 section of the License file that 022 * accompanied this code. If applicable, add the following below the License 023 * Header, with the fields enclosed by brackets [] replaced by your own 024 * identifying information: "Portions Copyrighted [year] 025 * [name of copyright owner]" 026 * 027 * Contributor(s): 028 * 029 * If you wish your version of this file to be governed by only the CDDL or 030 * only the GPL Version 2, indicate your decision by adding "[Contributor] 031 * elects to include this software in this distribution under the [CDDL or GPL 032 * Version 2] license." If you don't indicate a single choice of license, a 033 * recipient has the option to distribute your version of this file under 034 * either the CDDL, the GPL Version 2 or to extend the choice of license to 035 * its licensees as provided above. However, if you add GPL Version 2 code 036 * and therefore, elected the GPL Version 2 license, then the option applies 037 * only if the new code is made subject to such option by the copyright 038 * holder. 039 * 040 * Portions Copyrighted 2013 Nuxeo 041 */ 042 043package org.nuxeo.ecm.platform.ui.web.util; 044 045import java.io.IOException; 046import java.io.Writer; 047import java.util.regex.Matcher; 048import java.util.regex.Pattern; 049 050import javax.faces.FacesException; 051import javax.faces.component.UIComponent; 052import javax.faces.context.ExternalContext; 053import javax.faces.context.FacesContext; 054import javax.faces.context.ResponseWriter; 055 056import com.sun.faces.RIConstants; 057import com.sun.faces.config.WebConfiguration; 058import com.sun.faces.io.FastStringWriter; 059import com.sun.faces.renderkit.html_basic.HtmlResponseWriter; 060import com.sun.faces.util.HtmlUtils; 061import com.sun.faces.util.MessageUtils; 062 063/** 064 * CSV specific response writer copied pasted from com.sun.faces.renderkit.html_basic.HtmlResponseWriter without the 065 * HTML encode part. 066 * 067 * @since 5.9.1, 5.8-HF01 068 */ 069public class NXHtmlResponseWriter extends ResponseWriter { 070 071 // Content Type for this Writer. 072 // 073 private String contentType = "text/html"; 074 075 // Character encoding of that Writer - this may be null 076 // if the encoding isn't known. 077 // 078 private String encoding = null; 079 080 // Writer to use for output; 081 // 082 private Writer writer = null; 083 084 // True when we need to close a start tag 085 // 086 private boolean closeStart; 087 088 // Configuration flag regarding disableUnicodeEscaping 089 // 090 private WebConfiguration.DisableUnicodeEscaping disableUnicodeEscaping; 091 092 // Flag to escape Unicode 093 // 094 private boolean escapeUnicode; 095 096 // Flag to escape ISO-8859-1 codes 097 // 098 private boolean escapeIso; 099 100 // flag to indicate we're writing a CDATA section 101 private boolean writingCdata; 102 103 // flat to indicate the current element is CDATA 104 private boolean isCdata; 105 106 // flag to indicate that we're writing a 'script' or 'style' element 107 private boolean isScript; 108 109 // flag to indicate that we're writing a 'style' element 110 private boolean isStyle; 111 112 // flag to indicate that we're writing a 'src' attribute as part of 113 // 'script' or 'style' element 114 private boolean scriptOrStyleSrc; 115 116 // flag to indicate if the content type is Xhtml 117 private boolean isXhtml; 118 119 // HtmlResponseWriter to use when buffering is required 120 private Writer origWriter; 121 122 // Keep one instance of the script buffer per Writer 123 private FastStringWriter scriptBuffer; 124 125 // Keep one instance of attributesBuffer to buffer the writting 126 // of all attributes for a particular element to reducr the number 127 // of writes 128 private FastStringWriter attributesBuffer; 129 130 // Enables hiding of inlined script and style 131 // elements from old browsers 132 private Boolean isScriptHidingEnabled; 133 134 // Enables scripts to be included in attribute values 135 private Boolean isScriptInAttributeValueEnabled; 136 137 // Internal buffer used when outputting properly escaped information 138 // using HtmlUtils class. 139 // 140 private char[] buffer = new char[1028]; 141 142 // Internal buffer for to store the result of String.getChars() for 143 // values passed to the writer as String to reduce the overhead 144 // of String.charAt(). This buffer will be grown, if necessary, to 145 // accomodate larger values. 146 private char[] textBuffer = new char[128]; 147 148 static final Pattern CDATA_START_SLASH_SLASH; 149 150 static final Pattern CDATA_END_SLASH_SLASH; 151 152 static final Pattern CDATA_START_SLASH_STAR; 153 154 static final Pattern CDATA_END_SLASH_STAR; 155 156 static { 157 // At the beginning of a line, match // followed by any amount of 158 // whitespace, followed by <![CDATA[ 159 CDATA_START_SLASH_SLASH = Pattern.compile("^//\\s*\\Q<![CDATA[\\E"); 160 161 // At the end of a line, match // followed by any amout of whitespace, 162 // followed by ]]> 163 CDATA_END_SLASH_SLASH = Pattern.compile("//\\s*\\Q]]>\\E$"); 164 165 // At the beginning of a line, match /* followed by any amout of 166 // whitespace, followed by <![CDATA[, followed by any amount of 167 // whitespace, 168 // followed by */ 169 CDATA_START_SLASH_STAR = Pattern.compile("^/\\*\\s*\\Q<![CDATA[\\E\\s*\\*/"); 170 171 // At the end of a line, match /* followed by any amount of whitespace, 172 // followed by ]]> followed by any amount of whitespace, followed by */ 173 CDATA_END_SLASH_STAR = Pattern.compile("/\\*\\s*\\Q]]>\\E\\s*\\*/$"); 174 175 } 176 177 // ------------------------------------------------------------ 178 // Constructors 179 180 /** 181 * Constructor sets the <code>ResponseWriter</code> and encoding, and enables script hiding by default. 182 * 183 * @param writer the <code>ResponseWriter</code> 184 * @param contentType the content type. 185 * @param encoding the character encoding. 186 * @throws javax.faces.FacesException the encoding is not recognized. 187 */ 188 public NXHtmlResponseWriter(Writer writer, String contentType, String encoding) throws FacesException { 189 this(writer, contentType, encoding, null, null, null); 190 } 191 192 /** 193 * <p> 194 * Constructor sets the <code>ResponseWriter</code> and encoding. 195 * </p> 196 * <p> 197 * The argument configPrefs is a map of configurable prefs that affect this instance's behavior. Supported keys are: 198 * </p> 199 * <p> 200 * BooleanWebContextInitParameter.EnableJSStyleHiding: <code>true</code> if the writer should attempt to hide JS 201 * from older browsers 202 * </p> 203 * 204 * @param writer the <code>ResponseWriter</code> 205 * @param contentType the content type. 206 * @param encoding the character encoding. 207 * @throws javax.faces.FacesException the encoding is not recognized. 208 */ 209 public NXHtmlResponseWriter(Writer writer, String contentType, String encoding, Boolean isScriptHidingEnabled, 210 Boolean isScriptInAttributeValueEnabled, WebConfiguration.DisableUnicodeEscaping disableUnicodeEscaping) 211 throws FacesException { 212 213 this.writer = writer; 214 215 if (null != contentType) { 216 this.contentType = contentType; 217 } 218 219 this.encoding = encoding; 220 221 // init those configuration parameters not yet initialized 222 WebConfiguration webConfig = null; 223 if (isScriptHidingEnabled == null) { 224 webConfig = getWebConfiguration(webConfig); 225 isScriptHidingEnabled = (null == webConfig) ? WebConfiguration.BooleanWebContextInitParameter.EnableJSStyleHiding.getDefaultValue() 226 : webConfig.isOptionEnabled(WebConfiguration.BooleanWebContextInitParameter.EnableJSStyleHiding); 227 } 228 229 if (isScriptInAttributeValueEnabled == null) { 230 webConfig = getWebConfiguration(webConfig); 231 isScriptInAttributeValueEnabled = (null == webConfig) ? WebConfiguration.BooleanWebContextInitParameter.EnableScriptInAttributeValue.getDefaultValue() 232 : webConfig.isOptionEnabled(WebConfiguration.BooleanWebContextInitParameter.EnableScriptInAttributeValue); 233 } 234 235 if (disableUnicodeEscaping == null) { 236 webConfig = getWebConfiguration(webConfig); 237 disableUnicodeEscaping = WebConfiguration.DisableUnicodeEscaping.getByValue((null == webConfig) ? WebConfiguration.WebContextInitParameter.DisableUnicodeEscaping.getDefaultValue() 238 : webConfig.getOptionValue(WebConfiguration.WebContextInitParameter.DisableUnicodeEscaping)); 239 if (disableUnicodeEscaping == null) { 240 disableUnicodeEscaping = WebConfiguration.DisableUnicodeEscaping.False; 241 } 242 } 243 244 // and store them for later use 245 this.isScriptHidingEnabled = isScriptHidingEnabled; 246 this.isScriptInAttributeValueEnabled = isScriptInAttributeValueEnabled; 247 this.disableUnicodeEscaping = disableUnicodeEscaping; 248 249 attributesBuffer = new FastStringWriter(128); 250 251 // Check the character encoding 252 if (!HtmlUtils.validateEncoding(encoding)) { 253 throw new IllegalArgumentException( 254 MessageUtils.getExceptionMessageString(MessageUtils.ENCODING_ERROR_MESSAGE_ID)); 255 } 256 257 String charsetName = encoding.toUpperCase(); 258 259 switch (disableUnicodeEscaping) { 260 case True: 261 // html escape noting (except the dangerous characters like "<>'" 262 // etc 263 escapeUnicode = false; 264 escapeIso = false; 265 break; 266 case False: 267 // html escape any non-ascii character 268 escapeUnicode = true; 269 escapeIso = true; 270 break; 271 case Auto: 272 // is stream capable of rendering unicode, do not escape 273 escapeUnicode = !HtmlUtils.isUTFencoding(charsetName); 274 // is stream capable of rendering unicode or iso-8859-1, do not 275 // escape 276 escapeIso = !HtmlUtils.isISO8859_1encoding(charsetName) && !HtmlUtils.isUTFencoding(charsetName); 277 break; 278 } 279 } 280 281 private WebConfiguration getWebConfiguration(WebConfiguration webConfig) { 282 if (webConfig != null) { 283 return webConfig; 284 } 285 286 FacesContext context = FacesContext.getCurrentInstance(); 287 if (null != context) { 288 ExternalContext extContext = context.getExternalContext(); 289 if (null != extContext) { 290 webConfig = WebConfiguration.getInstance(extContext); 291 } 292 } 293 return webConfig; 294 } 295 296 // -------------------------------------------------- Methods From 297 // Closeable 298 299 /** Methods From <code>java.io.Writer</code> */ 300 301 @Override 302 public void close() throws IOException { 303 304 closeStartIfNecessary(); 305 writer.close(); 306 307 } 308 309 // -------------------------------------------------- Methods From 310 // Flushable 311 312 /** 313 * Flush any buffered output to the contained writer. 314 * 315 * @throws IOException if an input/output error occurs. 316 */ 317 @Override 318 public void flush() throws IOException { 319 320 // NOTE: Internal buffer's contents (the ivar "buffer") is 321 // written to the contained writer in the HtmlUtils class - see 322 // HtmlUtils.flushBuffer method; Buffering is done during 323 // writeAttribute/writeText - otherwise, output is written 324 // directly to the writer (ex: writer.write(....).. 325 // 326 // close any previously started element, if necessary 327 closeStartIfNecessary(); 328 329 } 330 331 // ---------------------------------------------------------- Public 332 // Methods 333 334 /** @return the content type such as "text/html" for this ResponseWriter. */ 335 @Override 336 public String getContentType() { 337 338 return contentType; 339 340 } 341 342 /** 343 * <p> 344 * Create a new instance of this <code>ResponseWriter</code> using a different <code>Writer</code>. 345 * 346 * @param writer The <code>Writer</code> that will be used to create another <code>ResponseWriter</code>. 347 */ 348 @Override 349 public ResponseWriter cloneWithWriter(Writer writer) { 350 351 try { 352 return new HtmlResponseWriter(writer, getContentType(), getCharacterEncoding(), isScriptHidingEnabled, 353 isScriptInAttributeValueEnabled, disableUnicodeEscaping, false); 354 } catch (FacesException e) { 355 // This should never happen 356 throw new IllegalStateException(); 357 } 358 359 } 360 361 /** Output the text for the end of a document. */ 362 @Override 363 public void endDocument() throws IOException { 364 365 writer.flush(); 366 367 } 368 369 /** 370 * <p> 371 * Write the end of an element. This method will first close any open element created by a call to 372 * <code>startElement()</code>. 373 * 374 * @param name Name of the element to be ended 375 * @throws IOException if an input/output error occurs 376 * @throws NullPointerException if <code>name</code> is <code>null</code> 377 */ 378 @Override 379 public void endElement(String name) throws IOException { 380 381 if (name == null) { 382 throw new NullPointerException(MessageUtils.getExceptionMessageString( 383 MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "name")); 384 } 385 386 isXhtml = getContentType().equals(RIConstants.XHTML_CONTENT_TYPE); 387 388 if (isScriptOrStyle(name) && !scriptOrStyleSrc && writer instanceof FastStringWriter) { 389 String result = ((FastStringWriter) writer).getBuffer().toString(); 390 writer = origWriter; 391 392 if (result != null) { 393 String trim = result.trim(); 394 if (isXhtml) { 395 if (isScript) { 396 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); 397 int trimLen = trim.length(), start, end; 398 // case 1 start is // end is // 399 if (cdataStartSlashSlash.find() && cdataEndSlashSlash.find()) { 400 start = cdataStartSlashSlash.end() - cdataStartSlashSlash.start(); 401 end = trimLen - (cdataEndSlashSlash.end() - cdataEndSlashSlash.start()); 402 writer.write(trim.substring(start, end)); 403 } 404 // case 2 start is // end is /* */ 405 else if ((null != cdataStartSlashSlash.reset() && cdataStartSlashSlash.find()) 406 && cdataEndSlashStar.find()) { 407 start = cdataStartSlashSlash.end() - cdataStartSlashSlash.start(); 408 end = trimLen - (cdataEndSlashStar.end() - cdataEndSlashStar.start()); 409 writer.write(trim.substring(start, end)); 410 } 411 // case 3 start is /* */ end is /* */ 412 else if (cdataStartSlashStar.find() 413 && (null != cdataEndSlashStar.reset() && cdataEndSlashStar.find())) { 414 start = cdataStartSlashStar.end() - cdataStartSlashStar.start(); 415 end = trimLen - (cdataEndSlashStar.end() - cdataEndSlashStar.start()); 416 writer.write(trim.substring(start, end)); 417 } 418 // case 4 start is /* */ end is // 419 else if ((null != cdataStartSlashStar.reset() && cdataStartSlashStar.find()) 420 && (null != cdataEndSlashStar.reset() && cdataEndSlashSlash.find())) { 421 start = cdataStartSlashStar.end() - cdataStartSlashStar.start(); 422 end = trimLen - (cdataEndSlashSlash.end() - cdataEndSlashSlash.start()); 423 writer.write(trim.substring(start, end)); 424 } 425 // case 5 no commented out cdata present. 426 else { 427 writer.write(result); 428 } 429 } else { 430 if (trim.startsWith("<![CDATA[") && trim.endsWith("]]>")) { 431 writer.write(trim.substring(9, trim.length() - 3)); 432 } else { 433 writer.write(result); 434 } 435 } 436 } else { 437 if (trim.startsWith("<!--") && trim.endsWith("//-->")) { 438 writer.write(trim.substring(4, trim.length() - 5)); 439 } else { 440 writer.write(result); 441 } 442 } 443 } 444 if (isXhtml) { 445 if (!writingCdata) { 446 if (isScript) { 447 writer.write("\n//]]>\n"); 448 } else { 449 writer.write("\n]]>\n"); 450 } 451 } 452 } else { 453 if (isScriptHidingEnabled) { 454 writer.write("\n//-->\n"); 455 } 456 } 457 } 458 isScript = false; 459 isStyle = false; 460 if ("cdata".equalsIgnoreCase(name)) { 461 writer.write("]]>"); 462 writingCdata = false; 463 isCdata = false; 464 return; 465 } 466 // See if we need to close the start of the last element 467 if (closeStart) { 468 boolean isEmptyElement = HtmlUtils.isEmptyElement(name); 469 470 // Tricky: we need to use the writer ivar here, rather than the 471 // one from the FacesContext because we don't want 472 // spurious /> characters to appear in the output. 473 if (isEmptyElement) { 474 flushAttributes(); 475 writer.write(" />"); 476 closeStart = false; 477 return; 478 } 479 flushAttributes(); 480 writer.write('>'); 481 closeStart = false; 482 } 483 484 writer.write("</"); 485 writer.write(name); 486 writer.write('>'); 487 488 } 489 490 /** 491 * @return the character encoding, such as "ISO-8859-1" for this ResponseWriter. Refer to: <a 492 * href="http://www.iana.org/assignments/character-sets" >theIANA</a> for a list of character encodings. 493 */ 494 @Override 495 public String getCharacterEncoding() { 496 497 return encoding; 498 499 } 500 501 /** 502 * <p> 503 * Write the text that should begin a response. 504 * </p> 505 * 506 * @throws IOException if an input/output error occurs 507 */ 508 @Override 509 public void startDocument() throws IOException { 510 511 // do nothing; 512 513 } 514 515 /** 516 * <p> 517 * Write the start of an element, up to and including the element name. Clients call <code>writeAttribute()</code> 518 * or <code>writeURIAttribute()</code> methods to add attributes after calling this method. 519 * 520 * @param name Name of the starting element 521 * @param componentForElement The UIComponent instance that applies to this element. This argument may be 522 * <code>null</code>. 523 * @throws IOException if an input/output error occurs 524 * @throws NullPointerException if <code>name</code> is <code>null</code> 525 */ 526 @Override 527 public void startElement(String name, UIComponent componentForElement) throws IOException { 528 529 if (name == null) { 530 throw new NullPointerException(MessageUtils.getExceptionMessageString( 531 MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "name")); 532 } 533 closeStartIfNecessary(); 534 isScriptOrStyle(name); 535 scriptOrStyleSrc = false; 536 if ("cdata".equalsIgnoreCase(name)) { 537 isCdata = true; 538 writingCdata = true; 539 writer.write("<![CDATA["); 540 closeStart = false; 541 return; 542 } else if (writingCdata) { 543 // starting an element within a cdata section, 544 // keep escaping disabled 545 isCdata = false; 546 writingCdata = true; 547 } 548 549 writer.write('<'); 550 writer.write(name); 551 closeStart = true; 552 553 } 554 555 @Override 556 public void write(char[] cbuf) throws IOException { 557 558 closeStartIfNecessary(); 559 writer.write(cbuf); 560 561 } 562 563 @Override 564 public void write(int c) throws IOException { 565 566 closeStartIfNecessary(); 567 writer.write(c); 568 569 } 570 571 @Override 572 public void write(String str) throws IOException { 573 574 closeStartIfNecessary(); 575 writer.write(str); 576 577 } 578 579 @Override 580 public void write(char[] cbuf, int off, int len) throws IOException { 581 582 closeStartIfNecessary(); 583 writer.write(cbuf, off, len); 584 585 } 586 587 @Override 588 public void write(String str, int off, int len) throws IOException { 589 590 closeStartIfNecessary(); 591 writer.write(str, off, len); 592 593 } 594 595 /** 596 * <p> 597 * Write a properly escaped attribute name and the corresponding value. The value text will be converted to a String 598 * if necessary. This method may only be called after a call to <code>startElement()</code>, and before the opened 599 * element has been closed. 600 * </p> 601 * 602 * @param name Attribute name to be added 603 * @param value Attribute value to be added 604 * @param componentPropertyName The name of the component property to which this attribute argument applies. This 605 * argument may be <code>null</code>. 606 * @throws IllegalStateException if this method is called when there is no currently open element 607 * @throws IOException if an input/output error occurs 608 * @throws NullPointerException if <code>name</code> is <code>null</code> 609 */ 610 @Override 611 public void writeAttribute(String name, Object value, String componentPropertyName) throws IOException { 612 613 if (name == null) { 614 throw new NullPointerException(MessageUtils.getExceptionMessageString( 615 MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "name")); 616 } 617 if (value == null) { 618 return; 619 } 620 621 if (isCdata) { 622 return; 623 } 624 625 if (name.equalsIgnoreCase("src") && isScriptOrStyle()) { 626 scriptOrStyleSrc = true; 627 } 628 629 Class valueClass = value.getClass(); 630 631 // Output Boolean values specially 632 if (valueClass == Boolean.class) { 633 if (Boolean.TRUE.equals(value)) { 634 // NOTE: HTML 4.01 states that boolean attributes 635 // may legally take a single value which is the 636 // name of the attribute itself or appear using 637 // minimization. 638 // http://www.w3.org/TR/html401/intro/sgmltut.html#h-3.3.4.2 639 attributesBuffer.write(' '); 640 attributesBuffer.write(name); 641 attributesBuffer.write("=\""); 642 attributesBuffer.write(name); 643 attributesBuffer.write('"'); 644 } 645 } else { 646 attributesBuffer.write(' '); 647 attributesBuffer.write(name); 648 attributesBuffer.write("=\""); 649 // write the attribute value 650 String val = value.toString(); 651 ensureTextBufferCapacity(val); 652 HtmlUtils.writeAttribute(attributesBuffer, escapeUnicode, escapeIso, buffer, val, textBuffer, 653 isScriptInAttributeValueEnabled); 654 attributesBuffer.write('"'); 655 } 656 657 } 658 659 /** 660 * <p> 661 * Write a comment string containing the specified text. The text will be converted to a String if necessary. If 662 * there is an open element that has been created by a call to <code>startElement()</code>, that element will be 663 * closed first. 664 * </p> 665 * 666 * @param comment Text content of the comment 667 * @throws IOException if an input/output error occurs 668 * @throws NullPointerException if <code>comment</code> is <code>null</code> 669 */ 670 @Override 671 public void writeComment(Object comment) throws IOException { 672 673 if (comment == null) { 674 throw new NullPointerException( 675 MessageUtils.getExceptionMessageString(MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID)); 676 } 677 678 if (writingCdata) { 679 return; 680 } 681 682 closeStartIfNecessary(); 683 // Don't include a trailing space after the '<!--' 684 // or a leading space before the '-->' to support 685 // IE conditional commentsoth 686 writer.write("<!--"); 687 writer.write(comment.toString()); 688 writer.write("-->"); 689 690 } 691 692 /** 693 * <p> 694 * Write a properly escaped single character, If there is an open element that has been created by a call to 695 * <code>startElement()</code>, that element will be closed first. 696 * </p> 697 * <p/> 698 * <p> 699 * All angle bracket occurrences in the argument must be escaped using the &gt; &lt; syntax. 700 * </p> 701 * 702 * @param text Text to be written 703 * @throws IOException if an input/output error occurs 704 */ 705 public void writeText(char text) throws IOException { 706 707 closeStartIfNecessary(); 708 writer.write(text); 709 } 710 711 /** 712 * <p> 713 * Write properly escaped text from a character array. The output from this command is identical to the invocation: 714 * <code>writeText(c, 0, c.length)</code>. If there is an open element that has been created by a call to 715 * <code>startElement()</code>, that element will be closed first. 716 * </p> 717 * </p> 718 * <p/> 719 * <p> 720 * All angle bracket occurrences in the argument must be escaped using the &gt; &lt; syntax. 721 * </p> 722 * 723 * @param text Text to be written 724 * @throws IOException if an input/output error occurs 725 * @throws NullPointerException if <code>text</code> is <code>null</code> 726 */ 727 public void writeText(char text[]) throws IOException { 728 729 if (text == null) { 730 throw new NullPointerException(MessageUtils.getExceptionMessageString( 731 MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "text")); 732 } 733 closeStartIfNecessary(); 734 writer.write(text); 735 736 } 737 738 /** 739 * <p> 740 * Write a properly escaped object. The object will be converted to a String if necessary. If there is an open 741 * element that has been created by a call to <code>startElement()</code>, that element will be closed first. 742 * </p> 743 * 744 * @param text Text to be written 745 * @param componentPropertyName The name of the component property to which this text argument applies. This 746 * argument may be <code>null</code>. 747 * @throws IOException if an input/output error occurs 748 * @throws NullPointerException if <code>text</code> is <code>null</code> 749 */ 750 @Override 751 public void writeText(Object text, String componentPropertyName) throws IOException { 752 753 if (text == null) { 754 throw new NullPointerException(MessageUtils.getExceptionMessageString( 755 MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "text")); 756 } 757 closeStartIfNecessary(); 758 writer.write(text.toString()); 759 } 760 761 /** 762 * <p> 763 * Write properly escaped text from a character array. If there is an open element that has been created by a call 764 * to <code>startElement()</code>, that element will be closed first. 765 * </p> 766 * <p/> 767 * <p> 768 * All angle bracket occurrences in the argument must be escaped using the &gt; &lt; syntax. 769 * </p> 770 * 771 * @param text Text to be written 772 * @param off Starting offset (zero-relative) 773 * @param len Number of characters to be written 774 * @throws IndexOutOfBoundsException if the calculated starting or ending position is outside the bounds of the 775 * character array 776 * @throws IOException if an input/output error occurs 777 * @throws NullPointerException if <code>text</code> is <code>null</code> 778 */ 779 @Override 780 public void writeText(char text[], int off, int len) throws IOException { 781 782 if (text == null) { 783 throw new NullPointerException(MessageUtils.getExceptionMessageString( 784 MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "text")); 785 } 786 if (off < 0 || off > text.length || len < 0 || len > text.length) { 787 throw new IndexOutOfBoundsException(); 788 } 789 closeStartIfNecessary(); 790 writer.write(text, off, len); 791 } 792 793 /** 794 * <p> 795 * Write a properly encoded URI attribute name and the corresponding value. The value text will be converted to a 796 * String if necessary). This method may only be called after a call to <code>startElement()</code>, and before the 797 * opened element has been closed. 798 * </p> 799 * 800 * @param name Attribute name to be added 801 * @param value Attribute value to be added 802 * @param componentPropertyName The name of the component property to which this attribute argument applies. This 803 * argument may be <code>null</code>. 804 * @throws IllegalStateException if this method is called when there is no currently open element 805 * @throws IOException if an input/output error occurs 806 * @throws NullPointerException if <code>name</code> or <code>value</code> is <code>null</code> 807 */ 808 @Override 809 public void writeURIAttribute(String name, Object value, String componentPropertyName) throws IOException { 810 811 if (name == null) { 812 throw new NullPointerException(MessageUtils.getExceptionMessageString( 813 MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "name")); 814 } 815 if (value == null) { 816 throw new NullPointerException(MessageUtils.getExceptionMessageString( 817 MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "value")); 818 } 819 820 if (isCdata) { 821 return; 822 } 823 824 if (name.equalsIgnoreCase("src") && isScriptOrStyle()) { 825 scriptOrStyleSrc = true; 826 } 827 828 attributesBuffer.write(' '); 829 attributesBuffer.write(name); 830 attributesBuffer.write("=\""); 831 832 String stringValue = value.toString(); 833 ensureTextBufferCapacity(stringValue); 834 // Javascript URLs should not be URL-encoded 835 if (stringValue.startsWith("javascript:")) { 836 HtmlUtils.writeAttribute(attributesBuffer, escapeUnicode, escapeIso, buffer, stringValue, textBuffer, 837 isScriptInAttributeValueEnabled); 838 } else { 839 HtmlUtils.writeURL(attributesBuffer, stringValue, textBuffer, encoding); 840 } 841 842 attributesBuffer.write('"'); 843 844 } 845 846 // --------------------------------------------------------- Private 847 // Methods 848 849 private void ensureTextBufferCapacity(String source) { 850 int len = source.length(); 851 if (textBuffer.length < len) { 852 textBuffer = new char[len * 2]; 853 } 854 } 855 856 /** 857 * This method automatically closes a previous element (if not already closed). 858 * 859 * @throws IOException if an error occurs writing 860 */ 861 private void closeStartIfNecessary() throws IOException { 862 863 if (closeStart) { 864 flushAttributes(); 865 writer.write('>'); 866 closeStart = false; 867 if (isScriptOrStyle() && !scriptOrStyleSrc) { 868 isXhtml = getContentType().equals(RIConstants.XHTML_CONTENT_TYPE); 869 if (isXhtml) { 870 if (!writingCdata) { 871 if (isScript) { 872 writer.write("\n//<![CDATA[\n"); 873 } else { 874 writer.write("\n<![CDATA[\n"); 875 } 876 } 877 } else { 878 if (isScriptHidingEnabled) { 879 writer.write("\n<!--\n"); 880 } 881 } 882 origWriter = writer; 883 if (scriptBuffer == null) { 884 scriptBuffer = new FastStringWriter(1024); 885 } 886 scriptBuffer.reset(); 887 writer = scriptBuffer; 888 isScript = false; 889 isStyle = false; 890 } 891 } 892 893 } 894 895 private void flushAttributes() throws IOException { 896 897 // a little complex, but the end result is, potentially, two 898 // fewer temp objects created per call. 899 StringBuilder b = attributesBuffer.getBuffer(); 900 int totalLength = b.length(); 901 if (totalLength != 0) { 902 int curIdx = 0; 903 while (curIdx < totalLength) { 904 if ((totalLength - curIdx) > buffer.length) { 905 int end = curIdx + buffer.length; 906 b.getChars(curIdx, end, buffer, 0); 907 writer.write(buffer); 908 curIdx += buffer.length; 909 } else { 910 int len = totalLength - curIdx; 911 b.getChars(curIdx, curIdx + len, buffer, 0); 912 writer.write(buffer, 0, len); 913 curIdx += len; 914 } 915 } 916 attributesBuffer.reset(); 917 } 918 919 } 920 921 private boolean isScriptOrStyle(String name) { 922 if ("script".equalsIgnoreCase(name)) { 923 isScript = true; 924 } else if ("style".equalsIgnoreCase(name)) { 925 isStyle = true; 926 } else { 927 isScript = false; 928 isStyle = false; 929 } 930 931 return (isScript || isStyle); 932 } 933 934 private boolean isScriptOrStyle() { 935 return (isScript || isStyle); 936 } 937}