| package com.primix.tapestry; |
| |
| import java.io.*; |
| import javax.servlet.ServletOutputStream; |
| import java.util.*; |
| import java.text.Format; |
| import java.text.NumberFormat; |
| |
| /* |
| * Tapestry Web Application Framework |
| * Copyright (c) 2000 by Howard Ship and Primix Solutions |
| * |
| * Primix Solutions |
| * One Arsenal Marketplace |
| * Watertown, MA 02472 |
| * http://www.primix.com |
| * mailto:hship@primix.com |
| * |
| * This library is free software. |
| * |
| * You may redistribute it and/or modify it under the terms of the GNU |
| * Lesser General Public License as published by the Free Software Foundation. |
| * |
| * Version 2.1 of the license should be included with this distribution in |
| * the file LICENSE, as well as License.html. If the license is not |
| * included with this distribution, you may find a copy at the FSF web |
| * site at 'www.gnu.org' or 'www.fsf.org', or you may write to the |
| * Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139 USA. |
| * |
| * This library 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 |
| * Lesser General Public License for more details. |
| * |
| */ |
| |
| /** |
| * This class is used to create HTML output. It is more sophisticated |
| * than <code>PrintWriter</code> in that it maintains a concept of the |
| * hierarchy of open HTML tags. It also supplies a number of other |
| * features that are useful when creating HTML. |
| * |
| * Elements are started with the {@link #begin(String)} |
| * or {@link #beginOrphan(String)} |
| * methods. Once they are started, attributes for the elements may be set with |
| * the various <code>attribute()</code> methods. The element is closed off |
| * (i.e., the closing '>' character is written) when any other method |
| * is invoked (exception: methods which do not produce output, such as |
| * {@link #flush()}). The <code>end()</code> methods end an element, |
| * writing an HTML close tag to the output. |
| * |
| * <p>The <code>HTMLResponseWriter</code> handles the necessary escaping |
| * of invalid characters. |
| * Specifically, the '<', '>' and '&' characters are properly |
| * converted to their HTML entities by the <code>print()</code> methods. |
| * Similar measures are taken by the {@link #attribute(String, String)} method. |
| * Other invalid characters are converted to their numeric entity equivalent. |
| * |
| * <p>The class provides some simple indentation support, which can be turned |
| * off when exact spacing is important. |
| * |
| * <p>This class makes it easy to generate trivial and non-trivial HTML pages. |
| * It is also useful to generate HTML snippets. It's ability to do simple |
| * formatting is very useful. A JSP may create an instance of the class |
| * and use it as an alternative to the simple-minded <b><%= ... %></b> |
| * construct, espcially because it can handle null more cleanly. |
| * |
| * <p>TBD: |
| * <ul> |
| * <li>Support XML and XHTML |
| * <li>Better control of indentation |
| * <li>What to do with Unicode characters with a value greater than 255? |
| * </ul> |
| * |
| * <p>This class is derived from the original class |
| * <code>com.primix.servlet.HTMLWriter</code> |
| * part of the <b>ServletUtils</b> framework available from |
| * <a href="http://www.gjt.org/servlets/JCVSlet/list/gjt/com/primix/servlet">The Giant |
| * Java Tree</a>. |
| * |
| * @version $Id$ |
| * @author Howard Ship |
| */ |
| |
| public class HTMLResponseWriter |
| implements IResponseWriter |
| { |
| private static String[] entities = new String[64]; |
| private static boolean[] safe = new boolean[128]; |
| |
| private static final String SAFE_CHARACTERS = |
| "01234567890" + |
| "abcdefghijklmnopqrstuvwxyz" + |
| "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + |
| "\t\n\r !\"#$%'()*+,-./:;=?@[\\]^_`{|}~"; |
| |
| static |
| { |
| int i; |
| int length; |
| |
| entities['"'] = """; |
| entities['<'] = "<"; |
| entities['>'] = ">"; |
| entities['&'] = "&"; |
| |
| length = SAFE_CHARACTERS.length(); |
| for (i = 0; i < length; i++) |
| safe[SAFE_CHARACTERS.charAt(i)] = true; |
| } |
| |
| |
| /** |
| * The underlying <code>PrintWriter</code> that output is sent to. |
| */ |
| |
| protected PrintWriter writer; |
| |
| /** |
| * Indicates whether a tag is open or not. A tag is opened by |
| * {@link #begin(String)} or {@link #beginOrphan(String)}. |
| * It stays open while calls to the <code>attribute()</code> |
| * methods are made. It is closed |
| * (the '>' is written) when any other method is invoked. |
| * |
| */ |
| |
| protected boolean openTag = false; |
| |
| /** |
| * A Stack of Strings used to track the active HTML elements. Elements are active |
| * until the corresponding close tag is written. The {@link #push(String)} method |
| * adds elements to the stack, {@link #pop()} removes them. |
| * |
| */ |
| |
| private Stack activeElementStack; |
| |
| /** |
| * The depth of the open tag stack. |
| * @see #activeElementStack |
| * |
| */ |
| |
| private int depth = 0; |
| |
| /** |
| * When compressed, automatic indentation is turned off so that |
| * unwanted spaces don't cause problems. |
| * |
| */ |
| |
| protected boolean compressed = false; |
| |
| private char[] buffer; |
| |
| /** |
| * Protected constructor, needed by {@link NestedHTMLResponseWriter}. |
| * |
| */ |
| |
| protected HTMLResponseWriter() |
| { |
| } |
| |
| /** |
| * Sends output to the stream. Internally, an instance of <code>PrintWriter</code> |
| * is created, which will be closed when the <code>HTMLResponseWriter</code> |
| * is closed. |
| * |
| */ |
| |
| public HTMLResponseWriter(OutputStream stream) |
| { |
| writer = new PrintWriter(stream); |
| } |
| |
| /** |
| * Creates a new <code>HTMLResponseWriter</code> around an existing |
| * {@link Writer}. A {@link PrintWriter} is created |
| * to handle most of the work of printing. |
| * |
| */ |
| |
| public HTMLResponseWriter(Writer writer) |
| { |
| this.writer = new PrintWriter(writer); |
| } |
| /** |
| * Simply prints the attribute name. This is used for |
| * idempotent attributes, such as 'disabled' in an |
| * <input>. |
| * |
| * <p>TBD: Check that name is legal. |
| * |
| * @throws IllegalStateException if there is no open tag. |
| */ |
| |
| public void attribute(String name) |
| { |
| checkTagOpen(); |
| |
| writer.print(' '); |
| writer.print(name); |
| } |
| |
| /** |
| * Writes an integer attribute into the currently open tag. |
| * |
| * <p>TBD: Validate that name is legal. |
| * |
| * @throws IllegalStateException if there is no open tag. |
| * |
| */ |
| |
| public void attribute(String name, int value) |
| { |
| checkTagOpen(); |
| |
| writer.print(' '); |
| writer.print(name); |
| writer.print("=\""); |
| writer.print(value); |
| writer.print('"'); |
| } |
| |
| /** |
| * Writes an attribute into the most recently opened tag. This must be called after |
| * {@link #begin(String)} |
| * and before any other kind of writing (which closes the tag). |
| * |
| * <p>The value may be null, in which case this method behaves the same as |
| * {@link #attribute(String)}. |
| * |
| * <p>Troublesome characters in the value are converted to thier HTML entities, much |
| * like a <code>print()</code> method, with the following exceptions: |
| * <ul> |
| * <li>The double quote (") is converted to &quot; |
| * <li>The ampersand (&) is passed through unchanged |
| * </ul> |
| * |
| * @throws IllegalStateException if there is no open tag. |
| */ |
| |
| |
| public void attribute(String name, String value) |
| { |
| checkTagOpen(); |
| int length; |
| |
| writer.print(' '); |
| |
| // Could use a check here that name contains only valid characters |
| |
| writer.print(name); |
| if (value == null) |
| return; |
| |
| length = value.length(); |
| |
| if (buffer == null || |
| buffer.length < length) |
| buffer = new char[length]; |
| |
| value.getChars(0, length, buffer, 0); |
| |
| // Have to assume that ANY attribute could be a URL and allow the ampersand |
| // as legit. |
| |
| writer.print("=\""); |
| safePrint(buffer, 0, length, true); |
| writer.print('"'); |
| } |
| |
| /** |
| * Closes any existing tag then starts a new element. The new element is pushed |
| * onto the active element stack. |
| */ |
| |
| public void begin(String name) |
| { |
| if (openTag) |
| closeTag(); |
| |
| push(name); |
| |
| indent(); |
| |
| writer.print('<'); |
| writer.print(name); |
| |
| openTag = true; |
| } |
| |
| /** |
| * Starts an element that will not later be matched with an <code>end()</code> |
| * call. This is useful for elements such as <hr;> or <br> that |
| * do not need closing tags. |
| * |
| */ |
| |
| public void beginOrphan(String name) |
| { |
| if (openTag) |
| closeTag(); |
| |
| // Pretend to push it onto the open tag stack just long enough |
| // to get the indentation right. |
| |
| depth++; |
| indent(); |
| depth--; |
| |
| writer.print('<'); |
| writer.print(name); |
| |
| openTag = true; |
| } |
| |
| /** |
| * Invokes <code>checkError()</code> on the |
| * <code>PrintWriter</code> used to format output. |
| */ |
| |
| public boolean checkError() |
| { |
| return writer.checkError(); |
| } |
| |
| private void checkTagOpen() |
| { |
| if (!openTag) |
| throw new IllegalStateException("A tag must be open before attributes " + |
| "may be set in an IResponseWriter."); |
| } |
| |
| /** |
| * Closes this <code>IResponseWriter</code>. Any active elements are closed. The |
| * {@link PrintWriter} is then sent {@link PrintWriter#close()}. |
| * |
| */ |
| |
| public void close() |
| { |
| String name; |
| |
| if (openTag) |
| closeTag(); |
| |
| // Close any active elements. |
| |
| while (depth > 0) |
| { |
| indent(); |
| name = pop(); |
| writer.print("</"); |
| writer.print(name); |
| writer.print('>'); |
| } |
| |
| writer.close(); |
| |
| writer = null; |
| activeElementStack = null; |
| buffer = null; |
| } |
| |
| /** |
| * Closes the most recently opened element by writing the '>' that ends |
| * it. Once this is invoked, the <code>attribute()</code> methods |
| * may not be used until a new element is opened with {@link #begin(String)} or |
| * or {@link #beginOrphan(String)}. |
| */ |
| |
| public void closeTag() |
| { |
| writer.print('>'); |
| |
| openTag = false; |
| } |
| |
| /** |
| * Writes an HTML comment. Any open tag is first closed. The comment |
| * is indented as if it were an HTML tag. The method takes care of |
| * providing the <code><!--</code> and <code>--></code>, but |
| * does not provide a blank line after the close of the comment. |
| * |
| * <p>Most characters are valid inside an HTML comment, so no check |
| * of the contents is made (much like {@link #printRaw(String)}. |
| * |
| */ |
| |
| public void comment(String value) |
| { |
| if (openTag) |
| closeTag(); |
| |
| indent(); |
| |
| writer.print("<!-- "); |
| writer.print(value); |
| writer.print(" -->"); |
| } |
| |
| /** |
| * Changes the compressed property, but returns the value before the |
| * change. This is used by clients that need to temporarily turn |
| * compression on, but want to restore it back to it's prior value. |
| */ |
| |
| public boolean compress(boolean value) |
| { |
| boolean old; |
| |
| old = compressed; |
| compressed = value; |
| |
| return old; |
| } |
| |
| /** |
| * Ends the element most recently started by {@link #begin(String)}. |
| * The name of the tag |
| * is popped off of the active element stack and used to form an HTML close tag. |
| * |
| * <p>TBD: Error checking for the open element stack empty. |
| */ |
| |
| public void end() |
| { |
| String name; |
| |
| if (openTag) |
| closeTag(); |
| |
| indent(); |
| |
| name = pop(); |
| |
| writer.print("</"); |
| writer.print(name); |
| writer.print('>'); |
| } |
| |
| /** |
| * Ends the most recently started element with the given name. This will |
| * also end any other intermediate elements. This is very useful for easily |
| * ending a table or even an entire page. |
| * |
| * <p>TBD: Error check if the name matches nothing on the open tag stack. |
| */ |
| |
| public void end(String name) |
| { |
| String tagName; |
| |
| if (openTag) |
| closeTag(); |
| |
| while (true) |
| { |
| indent(); |
| |
| tagName = pop(); |
| |
| writer.print("</"); |
| writer.print(tagName); |
| writer.print('>'); |
| |
| if (tagName.equals(name)) |
| break; |
| } |
| } |
| |
| /** |
| * Forwards <code>flush()</code> to this <code>HTMLResponseWriter</code>'s |
| * <code>PrintWriter</code>. |
| * |
| */ |
| |
| public void flush() |
| { |
| writer.flush(); |
| } |
| |
| public IResponseWriter getNestedWriter() |
| { |
| return new NestedHTMLResponseWriter(this); |
| } |
| |
| /** |
| * Prints a line seperator, then spaces to the |
| * current indentation depth (two spaces per level of depth). Does nothing if |
| * compressed. |
| * |
| */ |
| |
| protected final void indent() |
| { |
| int i; |
| |
| if (compressed) |
| return; |
| |
| writer.println(); |
| |
| // We want the outermost tag (typically, the 'html' tag) to not be indented, |
| // so we count from 1 not 0. |
| |
| for (i = 1; i < depth; i++) |
| writer.print(" "); |
| } |
| |
| public boolean isCompressed() |
| { |
| return compressed; |
| } |
| |
| /** |
| * Removes the top element from the active element stack and returns it. |
| * |
| */ |
| |
| protected final String pop() |
| { |
| String result; |
| |
| result = (String)activeElementStack.pop(); |
| depth--; |
| |
| return result; |
| } |
| |
| /** |
| * |
| * The primary <code>print()</code> method, used by most other methods. |
| * |
| * <p>Prints the character array, first closing any open tag. Problematic characters |
| * ('<', '>' and '&') are converted to their |
| * HTML entities. |
| * |
| * <p>All 'unsafe' characters are properly converted to either a named |
| * or numeric HTML entity. This can be somewhat expensive, so use |
| * {@link #printRaw(char[], int, int)} if the data to print is guarenteed |
| * to be safe. |
| * |
| * <p>Does <em>nothing</em> if <code>data</code> is null. |
| * |
| * <p>Closes any open tag. |
| * |
| */ |
| |
| public void print(char[] data, int offset, int length) |
| { |
| if (data == null) |
| return; |
| |
| if (openTag) |
| closeTag(); |
| |
| safePrint(data, offset, length, false); |
| } |
| |
| /** |
| * Prints a single character. If the character is not a 'safe' character, |
| * such as '<', then it's HTML entity (named or numeric) is printed instead. |
| * |
| * <p>Closes any open tag. |
| * |
| */ |
| |
| public void print(char value) |
| { |
| String entity = null; |
| |
| if (openTag) |
| closeTag(); |
| |
| if (value < safe.length && |
| safe[value]) |
| { |
| writer.print(value); |
| return; |
| } |
| |
| if (value < entities.length) |
| entity = entities[value]; |
| |
| if (entity != null) |
| { |
| writer.print(entity); |
| return; |
| } |
| |
| // Not a well-known entity. Print it's numeric equivalent. Note: this omits |
| // the leading '0', but most browsers (IE 5.0) don't seem to mind. Is this a bug? |
| |
| writer.print("&#" + (int)value + ";"); |
| } |
| |
| /** |
| * Prints an integer. |
| * |
| * <p>Closes any open tag. |
| * |
| */ |
| |
| public void print(int value) |
| { |
| if (openTag) |
| closeTag(); |
| |
| writer.print(value); |
| } |
| |
| /** |
| * Invokes {@link #print(char[], int, int)} to print the string. Use |
| * {@link #printRaw(String)} if the character data is known to be safe. |
| * |
| * <p>Does <em>nothing</em> if <code>value</code> is null. |
| * |
| * <p>Closes any open tag. |
| * |
| * @see #print(char[], int, int) |
| * |
| */ |
| |
| public void print(String value) |
| { |
| char[] data; |
| int length; |
| |
| if (value == null) |
| return; |
| |
| length = value.length(); |
| |
| if (buffer == null || |
| buffer.length < length) |
| buffer = new char[length]; |
| |
| value.getChars(0, length, buffer, 0); |
| |
| print(buffer, 0, length); |
| } |
| |
| /** |
| * Closes the open tag (if any), then prints a line seperator to the output stream. |
| * |
| */ |
| |
| public void println() |
| { |
| if (openTag) |
| closeTag(); |
| |
| writer.println(); |
| } |
| |
| /** |
| * Prints and portion of an output buffer to the stream. |
| * No escaping of invalid HTML elements is done, which |
| * makes this more effecient than <code>print()</code>. |
| * Does <em>nothing</em> if <code>buffer</code> |
| * is null. |
| * |
| * <p>Closes any open tag. |
| * |
| */ |
| |
| public void printRaw(char[] buffer, int offset, int length) |
| { |
| if (buffer == null) |
| return; |
| |
| if (openTag) |
| closeTag(); |
| |
| writer.write(buffer, offset, length); |
| } |
| |
| /** |
| * Prints output to the stream. No escaping of invalid HTML elements is done, which |
| * makes this more effecient than <code>print()</code>. Does <em>nothing</em> |
| * if <code>value</code> |
| * is null. |
| * |
| * <p>Closes any open tag. |
| * |
| */ |
| |
| public void printRaw(String value) |
| { |
| if (value == null) |
| return; |
| |
| if (openTag) |
| closeTag(); |
| |
| writer.print(value); |
| } |
| |
| /** |
| * Adds an element to the active element stack. |
| * |
| */ |
| |
| protected final void push(String name) |
| { |
| if (activeElementStack == null) |
| activeElementStack = new Stack(); |
| |
| activeElementStack.push(name); |
| |
| depth++; |
| } |
| |
| /** |
| * Internal support for safe printing. Ensures that all characters emitted |
| * are safe: either valid HTML characters or HTML entities (named or numeric). |
| */ |
| |
| private void safePrint(char[] data, int offset, int length, boolean isURL) |
| { |
| int i; |
| int start; |
| char ch; |
| int safelength = 0; |
| String entity; |
| boolean isSafe; |
| |
| start = offset; |
| |
| for (i = 0; i < length; i++) |
| { |
| ch = data[offset + i]; |
| |
| // Ignore safe characters. In an URL, ampersands |
| // are OK but quotes are not. Outside a URL, ampersands |
| // won't be in safe[], but quotes will. |
| |
| isSafe = (ch < safe.length && safe[ch]); |
| |
| if (isURL) |
| { |
| if (ch == '&') |
| isSafe = true; |
| else |
| if (ch == '"') |
| isSafe = false; |
| } |
| |
| if (isSafe) |
| { |
| safelength++; |
| continue; |
| } |
| |
| // Write the safe stuff. |
| |
| if (safelength > 0) |
| writer.write(data, start, safelength); |
| |
| entity = null; |
| |
| if (ch < entities.length) |
| entity = entities[ch]; |
| |
| if (entity == null) |
| entity = "&#" + (int)ch + ";"; |
| |
| writer.print(entity); |
| |
| start = offset + i + 1; |
| safelength = 0; |
| } |
| |
| if (safelength > 0) |
| writer.write(data, start, safelength); |
| } |
| |
| /** |
| * Changes the compressed attribute. When the <code>HTMLResponseWriter</code> |
| * is compressed, |
| * it does not perform any indentation. This is useful for elements, such |
| * as <textarea> or <pre> where exact spacing counts. |
| * |
| * @see #compress(boolean) |
| * |
| */ |
| |
| public void setCompressed(boolean value) |
| { |
| compressed = value; |
| } |
| |
| /** |
| * Returns <code>text/html</code>. |
| * |
| */ |
| |
| public String getContentType() |
| { |
| return "text/html"; |
| } |
| } |
| |