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