/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Business Objects nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* HTMLBuilder.java
* Creation date: Sep 28, 2005.
* By: Joseph Wong
*/
package org.openquark.cal.caldoc;
import javax.swing.text.html.HTML.Attribute;
import javax.swing.text.html.HTML.Tag;
import org.openquark.cal.util.ArrayStack;
/**
* This class implements a rudimentary builder for constructing HTML.
* It is based on a string builder, and does not build up a DOM for the entire document.
*
* @author Joseph Wong
*/
final class HTMLBuilder {
/** The string builder holding the HTML being built.*/
private final StringBuilder buffer = new StringBuilder();
/** Keeps track of the current indent level. */
private int indentLevel = 0;
/** The number of spaces to use per indent level. */
private static final int INDENT = 1;
/** The stack of HTML tags representing the current context (from the parent element all the way up to the root element). */
private final ArrayStack<Tag> tagStack = ArrayStack.make();
/** Keeps track of the immediately enclosing tag. */
private Tag currentTag = null;
/** Keeps track of whether a newline was just emitted (potentially suffixed with some whitespace). */
private boolean justEmittedNewline = false;
/**
* Represents a list of attributes for an HTML element.
*
* @author Joseph Wong
*/
final static class AttributeList {
/** The HTML representation of the attribute list. */
private final String htmlRep;
/**
* Private constructor.
* Factory methods should be used instead for constructing instances.
*
* @param htmlRep the HTML representation of the attribute list.
*/
private AttributeList(String htmlRep) {
this.htmlRep = htmlRep;
}
/**
* Creates an empty attribute list.
* @return an empty attribute list.
*/
static AttributeList make() {
return new AttributeList("");
}
/**
* Creates an attribute list of one attribute-value pair.
* @param attribute the attribute.
* @param value the value.
* @return a new attribute list.
*/
static AttributeList make(String attribute, String value) {
return new AttributeList(attribute + "='" + value + "'");
}
/**
* Creates an attribute list of one attribute-value pair.
* @param attribute the attribute.
* @param value the value.
* @return a new attribute list.
*/
static AttributeList make(Attribute attribute, String value) {
return make(attribute.toString(), value);
}
/**
* Creates an attribute list of two attribute-value pairs.
* @param attribute1 the first attribute.
* @param value1 the first value.
* @param attribute2 the second attribute.
* @param value2 the second value.
* @return a new attribute list.
*/
static AttributeList make(Attribute attribute1, String value1, Attribute attribute2, String value2) {
return make(attribute1, value1).concat(make(attribute2, value2));
}
/**
* Creates an attribute list of four attribute-value pairs.
* @param attribute1 the first attribute.
* @param value1 the first value.
* @param attribute2 the second attribute.
* @param value2 the second value.
* @param attribute3 the third attribute.
* @param value3 the third value.
* @return a new attribute list.
*/
static AttributeList make(Attribute attribute1, String value1, Attribute attribute2, String value2, Attribute attribute3, String value3) {
return make(attribute1, value1, attribute2, value2).concat(make(attribute3, value3));
}
/**
* Creates an attribute list of four attribute-value pairs.
* @param attribute1 the first attribute.
* @param value1 the first value.
* @param attribute2 the second attribute.
* @param value2 the second value.
* @param attribute3 the third attribute.
* @param value3 the third value.
* @param attribute4 the fourth attribute.
* @param value4 the fourth value.
* @return a new attribute list.
*/
static AttributeList make(Attribute attribute1, String value1, Attribute attribute2, String value2, Attribute attribute3, String value3, Attribute attribute4, String value4) {
return make(attribute1, value1, attribute2, value2, attribute3, value3).concat(make(attribute4, value4));
}
/**
* Creates a new attribute list out of the concatenation of this list with another list.
* @param other the other list.
* @return a new attribute list that is the concatenation of this list with the other list.
*/
AttributeList concat(AttributeList other) {
return new AttributeList(htmlRep + " " + other.htmlRep);
}
/**
* @return the HTML representation of the attribute list.
*/
String toHTML() {
return htmlRep;
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return htmlRep;
}
}
/**
* Returns whether the current context is to generate preformatted text.
* (For determining whether whitespace could be adjusted/inserted by the builder.)
*
* @return true if the current context is to generate preformatted text; false otherwise.
*/
private boolean inPreformattedText() {
if (currentTag == null) {
return false;
} else {
return currentTag.isPreformatted();
}
}
/**
* Writes an open tag for an element to the string builder, without adjusting indentation or inserting newlines.
* @param tag the tag.
* @param attributes the element's attributes.
*/
private void writeOpenTag(Tag tag, String attributes) {
buffer.append('<').append(tag.toString());
if (attributes != null && attributes.length() > 0) {
buffer.append(' ').append(attributes);
}
buffer.append('>');
tagStack.push(tag);
currentTag = tag;
}
/**
* Writes an open tag for a block element to the string builder.
* @param tag the tag.
* @return this HTMLBuilder instance.
*/
private HTMLBuilder openBlockTag(Tag tag) {
return openBlockTag(tag, null);
}
/**
* Writes an open tag for a block element to the string builder.
* @param tag the tag.
* @param attributes the element's attributes.
* @return this HTMLBuilder instance.
*/
private HTMLBuilder openBlockTag(Tag tag, String attributes) {
if (!justEmittedNewline) {
newlineAndIndent();
}
writeOpenTag(tag, attributes);
indentLevel += INDENT;
if (!tag.isPreformatted()) {
newlineAndIndent();
}
return this;
}
/**
* Writes an open tag for an inline element to the string builder.
* @param tag the tag.
* @return this HTMLBuilder instance.
*/
private HTMLBuilder openInlineTag(Tag tag) {
return openInlineTag(tag, null);
}
/**
* Writes an open tag for an inline element to the string builder.
* @param tag the tag.
* @param attributes the element's attributes.
* @return this HTMLBuilder instance.
*/
private HTMLBuilder openInlineTag(Tag tag, String attributes) {
//buffer.append(' ');
writeOpenTag(tag, attributes);
return this;
}
/**
* Writes an open tag for an element to the string builder.
* @param tag the tag.
* @return this HTMLBuilder instance.
*/
HTMLBuilder openTag(Tag tag) {
if (tag.isBlock() || tag.breaksFlow()) {
return openBlockTag(tag);
} else {
return openInlineTag(tag);
}
}
/**
* Writes an open tag for an element to the string builder.
* @param tag the tag.
* @param attributes the element's attributes.
* @return this HTMLBuilder instance.
*/
HTMLBuilder openTag(Tag tag, String attributes) {
if (tag.isBlock() || tag.breaksFlow()) {
return openBlockTag(tag, attributes);
} else {
return openInlineTag(tag, attributes);
}
}
/**
* Writes an open tag for an element to the string builder.
* @param tag the tag.
* @param attributes the element's attributes.
* @return this HTMLBuilder instance.
*/
HTMLBuilder openTag(Tag tag, AttributeList attributes) {
return openTag(tag, attributes.toHTML());
}
/**
* Writes a close tag for an element to the string builder, without adjusting indentation or inserting newlines.
* @param tag the tag.
*/
private void writeCloseTag(Tag tag) {
buffer.append("</").append(tag.toString()).append('>');
tagStack.pop();
if (tagStack.isEmpty()) {
currentTag = null;
} else {
currentTag = tagStack.peek();
}
}
/**
* Writes a close tag for a block element to the string builder.
* @param tag the tag.
* @return this HTMLBuilder instance.
*/
private HTMLBuilder closeBlockTag(Tag tag) {
indentLevel -= INDENT;
if (!tag.isPreformatted()) {
if (justEmittedNewline) {
int lenBeforePotentialIndent = buffer.length() - INDENT;
if (lenBeforePotentialIndent >= 0 && buffer.substring(lenBeforePotentialIndent).equals(spaces(INDENT))) {
buffer.setLength(lenBeforePotentialIndent);
}
} else {
newlineAndIndent();
}
}
writeCloseTag(tag);
newlineAndIndent();
return this;
}
/**
* Writes a close tag for an inline element to the string builder.
* @param tag the tag.
* @return this HTMLBuilder instance.
*/
private HTMLBuilder closeInlineTag(Tag tag) {
writeCloseTag(tag);
//buffer.append(' ');
return this;
}
/**
* Writes a close tag for an element to the string builder.
* @param tag the tag.
* @return this HTMLBuilder instance.
*/
HTMLBuilder closeTag(Tag tag) {
if (tag.isBlock() || tag.breaksFlow()) {
return closeBlockTag(tag);
} else {
return closeInlineTag(tag);
}
}
/**
* Writes a tag for an empty element to the string builder.
* @param tag the tag.
* @param attributes the element's attributes.
* @return this HTMLBuilder instance.
*/
HTMLBuilder emptyTag(Tag tag, String attributes) {
if (tag.isBlock() || tag.breaksFlow()) {
if (!justEmittedNewline) {
newlineAndIndent();
}
}
buffer.append('<').append(tag.toString());
if (attributes != null && attributes.length() > 0) {
buffer.append(' ').append(attributes);
}
buffer.append('>');
return this;
}
/**
* Writes a tag for an empty element to the string builder.
* @param tag the tag.
* @param attributes the element's attributes.
* @return this HTMLBuilder instance.
*/
HTMLBuilder emptyTag(Tag tag, AttributeList attributes) {
return emptyTag(tag, attributes.toHTML());
}
/**
* Writes a tag for an empty element to the string builder.
* @param tag the tag.
* @return this HTMLBuilder instance.
*/
HTMLBuilder emptyTag(Tag tag) {
return emptyTag(tag, (String)null);
}
/**
* Writes the given text to the buffer, properly indented to the current indent level.
* @param text the text to be written.
* @return this HTMLBuilder instance.
*/
private HTMLBuilder addIndentedText(String text) {
addUnindentedText(text.replaceAll("\\n", "\n" + spaces(indentLevel)));
return this;
}
/**
* Adds a newline and a proper indent to the buffer.
* @return this HTMLBuilder instance.
*/
private HTMLBuilder newlineAndIndent() {
newlineWithoutIndent();
indent();
return this;
}
/**
* Writes the given text to the buffer, without adjusting for indentation.
* @param text the text to be written.
* @return this HTMLBuilder instance.
*/
private HTMLBuilder addUnindentedText(String text) {
buffer.append(text);
if (text.length() > 0) {
char lastChar = text.charAt(text.length() - 1);
justEmittedNewline = (lastChar == '\n' || lastChar == '\r');
}
return this;
}
/**
* Adds a newline but without adding an indent afterwards.
* @return this HTMLBuilder instance.
*/
private HTMLBuilder newlineWithoutIndent() {
buffer.append('\n');
justEmittedNewline = true;
return this;
}
/**
* Writes the given text to the buffer, which will be optionally indented
* depending on whether the current context is to generate preformatted text (in which
* whitespace is significant and cannot be adjusted) or not.
*
* @param text the text to be written.
* @return this HTMLBuilder instance.
*/
HTMLBuilder addText(String text) {
if (inPreformattedText()) {
return addUnindentedText(text);
} else {
return addIndentedText(text);
}
}
/**
* Adds a newline to the buffer, which will be optionally followed by an indent
* depending on whether the current context is to generate preformatted text (in which
* whitespace is significant and cannot be adjusted) or not.
*
* @return this HTMLBuilder instance.
*/
HTMLBuilder newline() {
if (inPreformattedText()) {
return newlineWithoutIndent();
} else {
return newlineAndIndent();
}
}
/**
* Writes the given text wrapped by open and close tags to the buffer.
* @param tag the tag.
* @param text the text to be written.
* @return this HTMLBuilder instance.
*/
HTMLBuilder addTaggedText(Tag tag, String text) {
if (tag.isBlock() && !justEmittedNewline) {
newline();
}
openInlineTag(tag);
addText(text);
closeInlineTag(tag);
if (tag.isBlock()) {
newline();
}
return this;
}
/**
* Writes the given text wrapped by open and close tags to the buffer.
* @param tag the tag.
* @param attributes the element's attributes.
* @param text the text to be written.
* @return this HTMLBuilder instance.
*/
HTMLBuilder addTaggedText(Tag tag, String attributes, String text) {
openInlineTag(tag, attributes);
addText(text);
closeInlineTag(tag);
return this;
}
/**
* Writes the given text wrapped by open and close tags to the buffer.
* @param tag the tag.
* @param attributes the element's attributes.
* @param text the text to be written.
* @return this HTMLBuilder instance.
*/
HTMLBuilder addTaggedText(Tag tag, AttributeList attributes, String text) {
return addTaggedText(tag, attributes.toHTML(), text);
}
/**
* Adds an indent to the buffer.
*/
private void indent() {
for (int i = 0; i < indentLevel; i++) {
buffer.append(' ');
}
}
/**
* Creates a string of spaces.
* @param nSpaces the number of spaces in the generated string.
* @return a string of spaces.
*/
private String spaces(int nSpaces) {
StringBuilder buf = new StringBuilder();
for (int i = 0; i < nSpaces; i++) {
buf.append(' ');
}
return buf.toString();
}
/**
* @return the string representation of the HTML.
*/
String toHTML() {
return buffer.toString();
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return toHTML();
}
}