1 /* 2 * Copyright (c) 1996, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 import java.io.BufferedWriter; 25 import java.io.File; 26 import java.io.IOException; 27 import java.io.Writer; 28 import java.net.URL; 29 import java.text.MessageFormat; 30 import java.util.ResourceBundle; 31 32 /** 33 * A class to facilitate writing HTML via a stream. 34 */ 35 public class HTMLWriter 36 { 37 /** 38 * Create an HTMLWriter object, using a default doctype for HTML 3.2. 39 * @param out a Writer to which to write the generated HTML 40 * @throws IOException if there is a problem writing to the underlying stream 41 */ HTMLWriter(Writer out)42 public HTMLWriter(Writer out) throws IOException { 43 this(out, "<!DOCTYPE html\">"); 44 } 45 46 /** 47 * Create an HTMLWriter object, using a specifed doctype header. 48 * @param out a Writer to which to write the generated HTML 49 * @param docType a string containing a doctype header for the HTML to be generetaed 50 * @throws IOException if there is a problem writing to the underlying stream 51 */ HTMLWriter(Writer out, String docType)52 public HTMLWriter(Writer out, String docType) throws IOException { 53 if (out instanceof BufferedWriter) 54 this.out = (BufferedWriter) out; 55 else 56 this.out = new BufferedWriter(out); 57 this.out.write(docType); 58 this.out.newLine(); 59 } 60 61 /** 62 * Create an HTMLWriter object, using a specified bundle for localizing messages. 63 * @param out a Writer to which to write the generated HTML 64 * @param i18n a resource bundle to use to localize messages 65 * @throws IOException if there is a problem writing to the underlying stream 66 */ HTMLWriter(Writer out, ResourceBundle i18n)67 public HTMLWriter(Writer out, ResourceBundle i18n) throws IOException { 68 this(out); 69 this.i18n = i18n; 70 } 71 72 73 /** 74 * Create an HTMLWriter object, using a specifed doctype header and 75 * using a specified bundle for l0calizing messages. 76 * @param out a Writer to which to write the generated HTML 77 * @param docType a string containing a doctype header for the HTML to be generetaed 78 * @param i18n a resource bundle to use to localize messages 79 * @throws IOException if there is a problem writing to the underlying stream 80 */ HTMLWriter(Writer out, String docType, ResourceBundle i18n)81 public HTMLWriter(Writer out, String docType, ResourceBundle i18n) throws IOException { 82 this(out, docType); 83 this.i18n = i18n; 84 } 85 86 /** 87 * Set the reource bundle to be used for localizing messages. 88 * @param i18n the resource bundle to be used for localizing messages 89 */ setResourceBundle(ResourceBundle i18n)90 public void setResourceBundle(ResourceBundle i18n) { 91 this.i18n = i18n; 92 } 93 94 /** 95 * Flush the stream, and the underlying output stream. 96 * @throws IOException if there is a problem writing to the underlying stream 97 */ flush()98 public void flush() throws IOException { 99 out.flush(); 100 } 101 102 /** 103 * Close the stream, and the underlying output stream. 104 * @throws IOException if there is a problem closing the underlying stream 105 */ close()106 public void close() throws IOException { 107 out.close(); 108 } 109 110 /** 111 * Write a newline to the underlying output stream. 112 * @throws IOException if there is a problem writing to the underlying stream 113 */ newLine()114 public void newLine() throws IOException { 115 out.newLine(); 116 } 117 118 /** 119 * Start an HTML tag. If a prior tag has been started, it will 120 * be closed first. Once a tag has been opened, attributes for the 121 * tag may be written out, followed by body content before finally 122 * ending the tag. 123 * @param tag the tag to be started 124 * @throws IOException if there is a problem writing to the underlying stream 125 * @see #writeAttr 126 * @see #write 127 * @see #endTag 128 */ startTag(String tag)129 public void startTag(String tag) throws IOException { 130 if (state == IN_TAG) { 131 out.write(">"); 132 state = IN_BODY; 133 } 134 //newLine(); 135 out.write("<"); 136 out.write(tag); 137 state = IN_TAG; 138 } 139 140 /** 141 * Finish an HTML tag. It is expected that a call to endTag will match 142 * a corresponding earlier call to startTag, but there is no formal check 143 * for this. 144 * @param tag the tag to be closed. 145 * @throws IOException if there is a problem writing to the underlying stream 146 */ endTag(String tag)147 public void endTag(String tag) throws IOException { 148 if (state == IN_TAG) { 149 out.write(">"); 150 state = IN_BODY; 151 out.newLine(); 152 } 153 out.write("</"); 154 out.write(tag); 155 out.write(">"); 156 //out.newLine(); // PATCHED, jjg 157 state = IN_BODY; 158 } 159 160 /** 161 * Finish an empty element tag, such as a META, BASE or LINK tag. 162 * This is expected to correspond with a startTag. 163 * @param tag the tag which is being closed. this is only useful for 164 * validation, it is not written out 165 * @throws IllegalStateException if this call does not follow startTag 166 * (stream is not currently inside a tag) 167 * @throws IOException if there is a problem writing to the underlying stream 168 */ endEmptyTag(String tag)169 public void endEmptyTag(String tag) throws IOException { 170 if (state != IN_TAG) 171 throw new IllegalStateException(); 172 173 out.write(">"); 174 state = IN_BODY; 175 out.newLine(); 176 } 177 178 /** 179 * Write an attribute for a tag. A tag must previously have been started. 180 * All tag attributes must be written before any body text is written. 181 * The value will be quoted if necessary when writing it to the underlying 182 * stream. No check is made that the attribute is valid for the current tag. 183 * @param name the name of the attribute to be written 184 * @param value the value of the attribute to be written 185 * @throws IllegalStateException if the stream is not in a state to 186 * write attributes -- e.g. if this call does not follow startTag or other 187 * calls of writteAttr 188 * @throws IOException if there is a problem writing to the underlying stream 189 */ writeAttr(String name, String value)190 public void writeAttr(String name, String value) throws IOException { 191 if (state != IN_TAG) 192 throw new IllegalStateException(); 193 194 out.write(" "); 195 out.write(name); 196 out.write("="); 197 boolean alpha = true; 198 for (int i = 0; i < value.length() && alpha; i++) 199 alpha = Character.isLetter(value.charAt(i)); 200 if (!alpha) 201 out.write("\""); 202 out.write(value); 203 if (!alpha) 204 out.write("\""); 205 } 206 207 /** 208 * Write an attribute for a tag. A tag must previously have been started. 209 * All tag attributes must be written before any body text is written. 210 * The value will be quoted if necessary when writing it to the underlying 211 * stream. No check is made that the attribute is valid for the current tag. 212 * @param name the name of the attribute to be written 213 * @param value the value of the attribute to be written 214 * @throws IllegalStateException if the stream is not in a state to 215 * write attributes -- e.g. if this call does not follow startTag or other 216 * calls of writteAttr 217 * @throws IOException if there is a problem writing to the underlying stream 218 */ writeAttr(String name, int value)219 public void writeAttr(String name, int value) throws IOException { 220 writeAttr(name, Integer.toString(value)); 221 } 222 223 /** 224 * Write a line of text, followed by a newline. 225 * The text will be escaped as necessary. 226 * @param text the text to be written. 227 * @throws IOException if there is a problem closing the underlying stream 228 */ writeLine(String text)229 public void writeLine(String text) throws IOException { 230 write(text); 231 out.newLine(); 232 } 233 234 /** 235 * Write body text, escaping it as necessary. 236 * If this call follows a call of startTag, the open tag will be 237 * closed -- meaning that no more attributes can be written until another 238 * tag is started. If the text value is null, the current tag will still 239 * be closed, but no other text will be written. 240 * @param text the text to be written, may be null or zero length. 241 * @throws IOException if there is a problem writing to the underlying stream 242 */ write(String text)243 public void write(String text) throws IOException { 244 if (state == IN_TAG) { 245 out.write(">"); 246 state = IN_BODY; 247 } 248 249 if (text == null) 250 return; 251 252 // check to see if there are any special characters 253 boolean specialChars = false; 254 for (int i = 0; i < text.length() && !specialChars; i++) { 255 switch (text.charAt(i)) { 256 case '<': case '>': case '&': 257 specialChars = true; 258 } 259 } 260 261 // if there are special characters write the string character at a time; 262 // otherwise, write it out as is 263 if (specialChars) { 264 for (int i = 0; i < text.length(); i++) { 265 char c = text.charAt(i); 266 switch (c) { 267 case '<': out.write("<"); break; 268 case '>': out.write(">"); break; 269 case '&': out.write("&"); break; 270 default: out.write(c); 271 } 272 } 273 } 274 else 275 out.write(text); 276 } 277 278 /** 279 * Write a basic HTML entity, such as or { . 280 * @param entity the entity to write 281 * @throws IOException if there is a problem writing to the underlying stream 282 */ writeEntity(String entity)283 public void writeEntity(String entity) throws IOException { 284 if (state == IN_TAG) { 285 out.write(">"); 286 state = IN_BODY; 287 } 288 out.write(entity); 289 } 290 291 /** 292 * Write an image tag, using a specified path for the image source attribute. 293 * @param imagePath the path for the image source 294 * @throws IOException if there is a problem closing the underlying stream 295 */ writeImage(String imagePath)296 public void writeImage(String imagePath) throws IOException { 297 startTag(IMAGE); 298 writeAttr(SRC, imagePath); 299 } 300 301 /** 302 * Write an image tag, using a specified path for the image source attribute. 303 * @param imageURL the url for the image source 304 * @throws IOException if there is a problem closing the underlying stream 305 */ writeImage(URL imageURL)306 public void writeImage(URL imageURL) throws IOException { 307 writeImage(imageURL.toString()); 308 } 309 310 /** 311 * Write a hypertext link. 312 * @param anchor the target for the link 313 * @param body the body text for the link 314 * @throws IOException if there is a problem closing the underlying stream 315 */ writeLink(String anchor, String body)316 public void writeLink(String anchor, String body) throws IOException { 317 startTag(A); 318 writeAttr(HREF, anchor); 319 write(body); 320 endTag(A); 321 } 322 323 /** 324 * Write a hypertext link. 325 * @param file the target for the link 326 * @param body the body text for the link 327 * @throws IOException if there is a problem closing the underlying stream 328 */ writeLink(File file, String body)329 public void writeLink(File file, String body) throws IOException { 330 startTag(A); 331 StringBuilder sb = new StringBuilder(); 332 String path = file.getPath().replace(File.separatorChar, '/'); 333 if (file.isAbsolute() && !path.startsWith("/")) 334 sb.append('/'); 335 sb.append(path); 336 writeAttr(HREF, sb.toString()); 337 write(body); 338 endTag(A); 339 } 340 341 /** 342 * Write a hypertext link. 343 * @param file the target and body for the link 344 * @throws IOException if there is a problem closing the underlying stream 345 */ writeLink(File file)346 public void writeLink(File file) throws IOException { 347 writeLink(file, file.getPath()); 348 } 349 350 /** 351 * Write a hypertext link. 352 * @param url the target for the link 353 * @param body the body text for the link 354 * @throws IOException if there is a problem closing the underlying stream 355 */ writeLink(URL url, String body)356 public void writeLink(URL url, String body) throws IOException { 357 startTag(A); 358 writeAttr(HREF, url.toString()); 359 write(body); 360 endTag(A); 361 } 362 363 /** 364 * Write the destination marker for a hypertext link. 365 * @param anchor the destination marker for hypertext links 366 * @param body the body text for the marker 367 * @throws IOException if there is a problem closing the underlying stream 368 */ writeLinkDestination(String anchor, String body)369 public void writeLinkDestination(String anchor, String body) throws IOException { 370 startTag(A); 371 writeAttr(NAME, anchor); 372 write(body); 373 endTag(A); 374 } 375 376 /** 377 * Write a parameter tag. 378 * @param name the name of the parameter 379 * @param value the value of the parameter 380 * @throws IOException if there is a problem closing the underlying stream 381 */ writeParam(String name, String value)382 public void writeParam(String name, String value) throws IOException { 383 startTag(PARAM); 384 writeAttr(NAME, name); 385 writeAttr(VALUE, value); 386 } 387 388 /** 389 * Write a style attribute. 390 * @param value the value for the style atrtribute 391 * @throws IOException if there is a problem closing the underlying stream 392 */ writeStyleAttr(String value)393 public void writeStyleAttr(String value) throws IOException { 394 writeAttr(STYLE, value); 395 } 396 397 /** 398 * Write a localized message, using a specified resource bundle. 399 * @param i18n the resource bundle used to localize the message 400 * @param key the key for the message to be localized 401 * @throws IOException if there is a problem closing the underlying stream 402 */ write(ResourceBundle i18n, String key)403 public void write(ResourceBundle i18n, String key) throws IOException { 404 write(getString(i18n, key)); 405 } 406 407 /** 408 * Write a localized message, using a specified resource bundle. 409 * @param i18n the resource bundle used to localize the message 410 * @param key the key for the message to be localized 411 * @param arg an argument to be formatted into the localized message 412 * @throws IOException if there is a problem closing the underlying stream 413 */ write(ResourceBundle i18n, String key, Object arg)414 public void write(ResourceBundle i18n, String key, Object arg) throws IOException { 415 write(getString(i18n, key, arg)); 416 } 417 418 /** 419 * Write a localized message, using a specified resource bundle. 420 * @param i18n the resource bundle used to localize the message 421 * @param key the key for the message to be localized 422 * @param args arguments to be formatted into the localized message 423 * @throws IOException if there is a problem closing the underlying stream 424 */ write(ResourceBundle i18n, String key, Object[] args)425 public void write(ResourceBundle i18n, String key, Object[] args) throws IOException { 426 write(getString(i18n, key, args)); 427 } 428 429 /** 430 * Write a localized message, using the default resource bundle. 431 * @param key the key for the message to be localized 432 * @throws IOException if there is a problem closing the underlying stream 433 */ writeI18N(String key)434 public void writeI18N(String key) throws IOException { 435 write(getString(i18n, key)); 436 } 437 438 /** 439 * Write a localized message, using the default resource bundle. 440 * @param key the key for the message to be localized 441 * @param arg an argument to be formatted into the localized message 442 * @throws IOException if there is a problem closing the underlying stream 443 */ writeI18N(String key, Object arg)444 public void writeI18N(String key, Object arg) throws IOException { 445 write(getString(i18n, key, arg)); 446 } 447 448 /** 449 * Write a localized message, using the default resource bundle. 450 * @param key the key for the message to be localized 451 * @param args arguments to be formatted into the localized message 452 * @throws IOException if there is a problem closing the underlying stream 453 */ writeI18N(String key, Object[] args)454 public void writeI18N(String key, Object[] args) throws IOException { 455 write(getString(i18n, key, args)); 456 } 457 getString(ResourceBundle rb, String key, Object... args)458 private String getString(ResourceBundle rb, String key, Object... args) { 459 String s = rb.getString(key); 460 return MessageFormat.format(s, args); 461 } 462 463 /** The HTML "a" tag. */ 464 public static final String A = "a"; 465 /** The HTML "align" attribute. */ 466 public static final String ALIGN = "align"; 467 /** The HTML "b" tag. */ 468 public static final String B = "b"; 469 /** The HTML "body" tag. */ 470 public static final String BODY = "body"; 471 /** The HTML "border" attribute. */ 472 public static final String BORDER = "border"; 473 /** The HTML "br" tag. */ 474 public static final String BR = "br"; 475 /** The HTML "charset" attribute. */ 476 public static final String CHARSET = "charset"; 477 /** The HTML "class" attribute. */ 478 public static final String CLASS = "class"; 479 /** The HTML "classid" attribute. */ 480 public static final String CLASSID = "classid"; 481 /** The HTML "code" tag. */ 482 public static final String CODE = "code"; 483 /** The HTML "color" attribute. */ 484 public static final String COLOR = "color"; 485 /** The HTML "col" attribute value. */ 486 public static final String COL = "col"; 487 /** The HTML "dd" tag. */ 488 public static final String DD = "dd"; 489 /** The HTML "div" tag. */ 490 public static final String DIV = "div"; 491 /** The HTML "dl" tag. */ 492 public static final String DL = "dl"; 493 /** The HTML "dt" tag. */ 494 public static final String DT = "dt"; 495 /** The HTML "font" tag. */ 496 public static final String FONT = "font"; 497 /** The HTML "h1" tag. */ 498 public static final String H1 = "h1"; 499 /** The HTML "h2" tag. */ 500 public static final String H2 = "h2"; 501 /** The HTML "h3" tag. */ 502 public static final String H3 = "h3"; 503 /** The HTML "h4" tag. */ 504 public static final String H4 = "h4"; 505 /** The HTML "h5" tag. */ 506 public static final String H5 = "h5"; 507 /** The HTML "head" tag. */ 508 public static final String HEAD = "head"; 509 /** The HTML "href" attribute. */ 510 public static final String HREF = "href"; 511 /** The HTML "html" tag. */ 512 public static final String HTML = "html"; 513 /** The HTML "hr" tag. */ 514 public static final String HR = "hr"; 515 /** The HTML "i" tag. */ 516 public static final String I = "i"; 517 /** The HTML "id" tag. */ 518 public static final String ID = "id"; 519 /** The HTML "image" tag. */ 520 public static final String IMAGE = "image"; 521 /** The HTML "left" attribute value. */ 522 public static final String LEFT = "left"; 523 /** The HTML "li" tag. */ 524 public static final String LI = "li"; 525 /** The HTML "link" tag. */ 526 public static final String LINK = "link"; 527 /** The HTML "meta" attribute. */ 528 public static final String META = "meta"; 529 /** The HTML "name" attribute. */ 530 public static final String NAME = "name"; 531 /** The HTML "object" tag. */ 532 public static final String OBJECT = "object"; 533 /** The HTML "p" tag. */ 534 public static final String PARAM = "param"; 535 /** The HTML "param" tag. */ 536 public static final String P = "p"; 537 /** The HTML "rel" attribute value. */ 538 public static final String REL = "rel"; 539 /** The HTML "right" attribute value. */ 540 public static final String RIGHT = "right"; 541 /** The HTML "row" attribute value. */ 542 public static final String ROW = "row"; 543 /** The HTML "script" tag. */ 544 public static final String SCRIPT = "script"; 545 /** The HTML "small" tag. */ 546 public static final String SMALL = "small"; 547 /** The HTML "span" tag. */ 548 public static final String SPAN = "span"; 549 /** The HTML "src" attribute. */ 550 public static final String SRC = "src"; 551 /** The HTML "scope" attribute. */ 552 public static final String SCOPE = "scope"; 553 /** The HTML "style" attribute. */ 554 public static final String STYLE = "style"; 555 /** The HTML "table" tag. */ 556 public static final String TABLE = "table"; 557 /** The HTML "td" tag. */ 558 public static final String TD = "td"; 559 /** The HTML type for JavaScript. */ 560 public static final String TEXT_JAVASCRIPT = "text/javascript"; 561 /** The HTML "title"attribute. */ 562 public static final String TITLE = "title"; 563 /** The HTML "th" tag. */ 564 public static final String TH = "th"; 565 /** The HTML "top" attribute value. */ 566 public static final String TOP = "top"; 567 /** The HTML "tr" tag. */ 568 public static final String TR = "tr"; 569 /** The HTML "type" attribute. */ 570 public static final String TYPE = "type"; 571 /** The HTML "ul" tag. */ 572 public static final String UL = "ul"; 573 /** The HTML "valign" attribute. */ 574 public static final String VALIGN = "valign"; 575 /** The HTML "value" attribute. */ 576 public static final String VALUE = "value"; 577 578 579 private BufferedWriter out; 580 private int state; 581 private ResourceBundle i18n; 582 private static final int IN_TAG = 1; 583 private static final int IN_BODY = 2; 584 } 585