/* * Copyright (c) 1996,2010, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import java.io.BufferedWriter; import java.io.File; import java.io.IOException; import java.io.Writer; import java.net.URL; import java.text.MessageFormat; import java.util.ResourceBundle; /** * A class to facilitate writing HTML via a stream. */ public class HTMLWriter { /** * Create an HTMLWriter object, using a default doctype for HTML 3.2. * @param out a Writer to which to write the generated HTML * @throws IOException if there is a problem writing to the underlying stream */ public HTMLWriter(Writer out) throws IOException { this(out, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2//EN\">"); } /** * Create an HTMLWriter object, using a specifed doctype header. * @param out a Writer to which to write the generated HTML * @param docType a string containing a doctype header for the HTML to be generetaed * @throws IOException if there is a problem writing to the underlying stream */ public HTMLWriter(Writer out, String docType) throws IOException { if (out instanceof BufferedWriter) this.out = (BufferedWriter) out; else this.out = new BufferedWriter(out); this.out.write(docType); this.out.newLine(); } /** * Create an HTMLWriter object, using a specified bundle for localizing messages. * @param out a Writer to which to write the generated HTML * @param i18n a resource bundle to use to localize messages * @throws IOException if there is a problem writing to the underlying stream */ public HTMLWriter(Writer out, ResourceBundle i18n) throws IOException { this(out); this.i18n = i18n; } /** * Create an HTMLWriter object, using a specifed doctype header and * using a specified bundle for l0calizing messages. * @param out a Writer to which to write the generated HTML * @param docType a string containing a doctype header for the HTML to be generetaed * @param i18n a resource bundle to use to localize messages * @throws IOException if there is a problem writing to the underlying stream */ public HTMLWriter(Writer out, String docType, ResourceBundle i18n) throws IOException { this(out, docType); this.i18n = i18n; } /** * Set the reource bundle to be used for localizing messages. * @param i18n the resource bundle to be used for localizing messages */ public void setResourceBundle(ResourceBundle i18n) { this.i18n = i18n; } /** * Flush the stream, and the underlying output stream. * @throws IOException if there is a problem writing to the underlying stream */ public void flush() throws IOException { out.flush(); } /** * Close the stream, and the underlying output stream. * @throws IOException if there is a problem closing the underlying stream */ public void close() throws IOException { out.close(); } /** * Write a newline to the underlying output stream. * @throws IOException if there is a problem writing to the underlying stream */ public void newLine() throws IOException { out.newLine(); } /** * Start an HTML tag. If a prior tag has been started, it will * be closed first. Once a tag has been opened, attributes for the * tag may be written out, followed by body content before finally * ending the tag. * @param tag the tag to be started * @throws IOException if there is a problem writing to the underlying stream * @see #writeAttr * @see #write * @see #endTag */ public void startTag(String tag) throws IOException { if (state == IN_TAG) { out.write(">"); state = IN_BODY; } //newLine(); out.write("<"); out.write(tag); state = IN_TAG; } /** * Finish an HTML tag. It is expected that a call to endTag will match * a corresponding earlier call to startTag, but there is no formal check * for this. * @param tag the tag to be closed. * @throws IOException if there is a problem writing to the underlying stream */ public void endTag(String tag) throws IOException { if (state == IN_TAG) { out.write(">"); state = IN_BODY; out.newLine(); } out.write("</"); out.write(tag); out.write(">"); //out.newLine(); // PATCHED, jjg state = IN_BODY; } /** * Finish an empty element tag, such as a META, BASE or LINK tag. * This is expected to correspond with a startTag. * @param tag the tag which is being closed. this is only useful for * validation, it is not written out * @throws IllegalStateException if this call does not follow startTag * (stream is not currently inside a tag) * @throws IOException if there is a problem writing to the underlying stream */ public void endEmptyTag(String tag) throws IOException { if (state != IN_TAG) throw new IllegalStateException(); out.write(">"); state = IN_BODY; out.newLine(); } /** * Write an attribute for a tag. A tag must previously have been started. * All tag attributes must be written before any body text is written. * The value will be quoted if necessary when writing it to the underlying * stream. No check is made that the attribute is valid for the current tag. * @param name the name of the attribute to be written * @param value the value of the attribute to be written * @throws IllegalStateException if the stream is not in a state to * write attributes -- e.g. if this call does not follow startTag or other * calls of writteAttr * @throws IOException if there is a problem writing to the underlying stream */ public void writeAttr(String name, String value) throws IOException { if (state != IN_TAG) throw new IllegalStateException(); out.write(" "); out.write(name); out.write("="); boolean alpha = true; for (int i = 0; i < value.length() && alpha; i++) alpha = Character.isLetter(value.charAt(i)); if (!alpha) out.write("\""); out.write(value); if (!alpha) out.write("\""); } /** * Write an attribute for a tag. A tag must previously have been started. * All tag attributes must be written before any body text is written. * The value will be quoted if necessary when writing it to the underlying * stream. No check is made that the attribute is valid for the current tag. * @param name the name of the attribute to be written * @param value the value of the attribute to be written * @throws IllegalStateException if the stream is not in a state to * write attributes -- e.g. if this call does not follow startTag or other * calls of writteAttr * @throws IOException if there is a problem writing to the underlying stream */ public void writeAttr(String name, int value) throws IOException { writeAttr(name, Integer.toString(value)); } /** * Write a line of text, followed by a newline. * The text will be escaped as necessary. * @param text the text to be written. * @throws IOException if there is a problem closing the underlying stream */ public void writeLine(String text) throws IOException { write(text); out.newLine(); } /** * Write body text, escaping it as necessary. * If this call follows a call of startTag, the open tag will be * closed -- meaning that no more attributes can be written until another * tag is started. If the text value is null, the current tag will still * be closed, but no other text will be written. * @param text the text to be written, may be null or zero length. * @throws IOException if there is a problem writing to the underlying stream */ public void write(String text) throws IOException { if (state == IN_TAG) { out.write(">"); state = IN_BODY; } if (text == null) return; // check to see if there are any special characters boolean specialChars = false; for (int i = 0; i < text.length() && !specialChars; i++) { switch (text.charAt(i)) { case '<': case '>': case '&': specialChars = true; } } // if there are special characters write the string character at a time; // otherwise, write it out as is if (specialChars) { for (int i = 0; i < text.length(); i++) { char c = text.charAt(i); switch (c) { case '<': out.write("<"); break; case '>': out.write(">"); break; case '&': out.write("&"); break; default: out.write(c); } } } else out.write(text); } /** * Write a basic HTML entity, such as   or { . * @param entity the entity to write * @throws IOException if there is a problem writing to the underlying stream */ public void writeEntity(String entity) throws IOException { if (state == IN_TAG) { out.write(">"); state = IN_BODY; } out.write(entity); } /** * Write an image tag, using a specified path for the image source attribute. * @param imagePath the path for the image source * @throws IOException if there is a problem closing the underlying stream */ public void writeImage(String imagePath) throws IOException { startTag(IMAGE); writeAttr(SRC, imagePath); } /** * Write an image tag, using a specified path for the image source attribute. * @param imageURL the url for the image source * @throws IOException if there is a problem closing the underlying stream */ public void writeImage(URL imageURL) throws IOException { writeImage(imageURL.toString()); } /** * Write a hypertext link. * @param anchor the target for the link * @param body the body text for the link * @throws IOException if there is a problem closing the underlying stream */ public void writeLink(String anchor, String body) throws IOException { startTag(A); writeAttr(HREF, anchor); write(body); endTag(A); } /** * Write a hypertext link. * @param file the target for the link * @param body the body text for the link * @throws IOException if there is a problem closing the underlying stream */ public void writeLink(File file, String body) throws IOException { startTag(A); StringBuffer sb = new StringBuffer(); String path = file.getPath().replace(File.separatorChar, '/'); if (file.isAbsolute() && !path.startsWith("/")) sb.append('/'); sb.append(path); writeAttr(HREF, sb.toString()); write(body); endTag(A); } /** * Write a hypertext link. * @param file the target and body for the link * @throws IOException if there is a problem closing the underlying stream */ public void writeLink(File file) throws IOException { writeLink(file, file.getPath()); } /** * Write a hypertext link. * @param url the target for the link * @param body the body text for the link * @throws IOException if there is a problem closing the underlying stream */ public void writeLink(URL url, String body) throws IOException { startTag(A); writeAttr(HREF, url.toString()); write(body); endTag(A); } /** * Write the destination marker for a hypertext link. * @param anchor the destination marker for hypertext links * @param body the body text for the marker * @throws IOException if there is a problem closing the underlying stream */ public void writeLinkDestination(String anchor, String body) throws IOException { startTag(A); writeAttr(NAME, anchor); write(body); endTag(A); } /** * Write a parameter tag. * @param name the name of the parameter * @param value the value of the parameter * @throws IOException if there is a problem closing the underlying stream */ public void writeParam(String name, String value) throws IOException { startTag(PARAM); writeAttr(NAME, name); writeAttr(VALUE, value); } /** * Write a style attribute. * @param value the value for the style atrtribute * @throws IOException if there is a problem closing the underlying stream */ public void writeStyleAttr(String value) throws IOException { writeAttr(STYLE, value); } /** * Write a localized message, using a specified resource bundle. * @param i18n the resource bundle used to localize the message * @param key the key for the message to be localized * @throws IOException if there is a problem closing the underlying stream */ public void write(ResourceBundle i18n, String key) throws IOException { write(getString(i18n, key)); } /** * Write a localized message, using a specified resource bundle. * @param i18n the resource bundle used to localize the message * @param key the key for the message to be localized * @param arg an argument to be formatted into the localized message * @throws IOException if there is a problem closing the underlying stream */ public void write(ResourceBundle i18n, String key, Object arg) throws IOException { write(getString(i18n, key, arg)); } /** * Write a localized message, using a specified resource bundle. * @param i18n the resource bundle used to localize the message * @param key the key for the message to be localized * @param args arguments to be formatted into the localized message * @throws IOException if there is a problem closing the underlying stream */ public void write(ResourceBundle i18n, String key, Object[] args) throws IOException { write(getString(i18n, key, args)); } /** * Write a localized message, using the default resource bundle. * @param key the key for the message to be localized * @throws IOException if there is a problem closing the underlying stream */ public void writeI18N(String key) throws IOException { write(getString(i18n, key)); } /** * Write a localized message, using the default resource bundle. * @param key the key for the message to be localized * @param arg an argument to be formatted into the localized message * @throws IOException if there is a problem closing the underlying stream */ public void writeI18N(String key, Object arg) throws IOException { write(getString(i18n, key, arg)); } /** * Write a localized message, using the default resource bundle. * @param key the key for the message to be localized * @param args arguments to be formatted into the localized message * @throws IOException if there is a problem closing the underlying stream */ public void writeI18N(String key, Object[] args) throws IOException { write(getString(i18n, key, args)); } private String getString(ResourceBundle rb, String key, Object... args) { String s = rb.getString(key); return MessageFormat.format(s, args); } /** The HTML "a" tag. */ public static final String A = "a"; /** The HTML "align" attribute. */ public static final String ALIGN = "align"; /** The HTML "b" tag. */ public static final String B = "b"; /** The HTML "body" tag. */ public static final String BODY = "body"; /** The HTML "border" attribute. */ public static final String BORDER = "border"; /** The HTML "br" tag. */ public static final String BR = "br"; /** The HTML "class" attribute. */ public static final String CLASS = "class"; /** The HTML "classid" attribute. */ public static final String CLASSID = "classid"; /** The HTML "code" tag. */ public static final String CODE = "code"; /** The HTML "color" attribte. */ public static final String COLOR = "color"; /** The HTML "col" attribute value. */ public static final String COL = "col"; /** The HTML "dd" tag. */ public static final String DD = "dd"; /** The HTML "div" tag. */ public static final String DIV = "div"; /** The HTML "dl" tag. */ public static final String DL = "dl"; /** The HTML "dt" tag. */ public static final String DT = "dt"; /** The HTML "font" tag. */ public static final String FONT = "font"; /** The HTML "h1" tag. */ public static final String H1 = "h1"; /** The HTML "h2" tag. */ public static final String H2 = "h2"; /** The HTML "h3" tag. */ public static final String H3 = "h3"; /** The HTML "h4" tag. */ public static final String H4 = "h4"; /** The HTML "h5" tag. */ public static final String H5 = "h5"; /** The HTML "head" tag. */ public static final String HEAD = "head"; /** The HTML "href" attribute. */ public static final String HREF = "href"; /** The HTML "html" tag. */ public static final String HTML = "html"; /** The HTML "hr" tag. */ public static final String HR = "hr"; /** The HTML "i" tag. */ public static final String I = "i"; /** The HTML "id" tag. */ public static final String ID = "id"; /** The HTML "image" tag. */ public static final String IMAGE = "image"; /** The HTML "left" attribute value. */ public static final String LEFT = "left"; /** The HTML "li" tag. */ public static final String LI = "li"; /** The HTML "link" tag. */ public static final String LINK = "link"; /** The HTML "name" attribute. */ public static final String NAME = "name"; /** The HTML "object" tag. */ public static final String OBJECT = "object"; /** The HTML "p" tag. */ public static final String PARAM = "param"; /** The HTML "param" tag. */ public static final String P = "p"; /** The HTML "rel" attribute value. */ public static final String REL = "rel"; /** The HTML "right" attribute value. */ public static final String RIGHT = "right"; /** The HTML "row" attribute value. */ public static final String ROW = "row"; /** The HTML "script" tag. */ public static final String SCRIPT = "script"; /** The HTML "small" tag. */ public static final String SMALL = "small"; /** The HTML "span" tag. */ public static final String SPAN = "span"; /** The HTML "src" attribute. */ public static final String SRC = "src"; /** The HTML "scope" attribute. */ public static final String SCOPE = "scope"; /** The HTML "style" attribute. */ public static final String STYLE = "style"; /** The HTML "table" tag. */ public static final String TABLE = "table"; /** The HTML "td" tag. */ public static final String TD = "td"; /** The HTML type for JavaScript. */ public static final String TEXT_JAVASCRIPT = "text/javascript"; /** The HTML "title"attribute. */ public static final String TITLE = "title"; /** The HTML "th" tag. */ public static final String TH = "th"; /** The HTML "top" attribute value. */ public static final String TOP = "top"; /** The HTML "tr" tag. */ public static final String TR = "tr"; /** The HTML "type" attribute. */ public static final String TYPE = "type"; /** The HTML "ul" tag. */ public static final String UL = "ul"; /** The HTML "valign" attribute. */ public static final String VALIGN = "valign"; /** The HTML "value" attribute. */ public static final String VALUE = "value"; private BufferedWriter out; private int state; private ResourceBundle i18n; private static final int IN_TAG = 1; private static final int IN_BODY = 2; }