/*
* Copyright 2008 Fedora Commons, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mulgara.webquery.html;
import java.io.PrintWriter;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.mulgara.util.functional.C;
/**
* Represents an HTML element that can be written to an output stream.
* Indents are hard coded to 2 characters.
*
* @created Aug 4, 2008
* @author Paula Gearon
* @copyright © 2008 <a href="http://www.fedora-commons.org/">Fedora Commons</a>
*/
public abstract class HtmlElement {
/** The amount to indent by. */
private static final String INDENT_CHARS = " ";
/** The number of indents to use for this element. */
protected int indent;
/** The current indent string, where indentStr = {@link #INDENT_CHARS}*{@link #indent}. */
private String indentStr;
/** The list of elements under this element */
protected List<HtmlElement> subElements;
/** The attributes for this element */
protected Map<Attr,String> attributes;
/** A limited list of attributes. Expand on this as required. */
public enum Attr {
CLASS,
ID,
STYLE,
TITLE,
NAME,
HEIGHT,
WIDTH,
SIZE,
ALIGN,
VALIGN,
BORDER,
CELLPADDING,
CELLSPACING,
COLS,
ROWS,
COLSPAN,
ALT,
HREF,
SELECTED,
VALUE,
METHOD,
SUMMARY;
public String toString() { return this.name().toLowerCase(); };
};
/** A limited list of entities. Expand on this as required. */
public enum Entity {
AMP("&"),
GT(">"),
LT("<"),
QUOT("""),
APOS("'"), // ' does not work in IE
NBSP(" ");
private final String t;
private Entity(String t) { this.t = t; }
public String toString() { return t; }
};
/**
* Creates an empty element with a given indent.
* @param indent The amount to indent by.
*/
HtmlElement(int indent) {
this.indent = indent;
indentStr = generateIndent(indent);
subElements = new LinkedList<HtmlElement>();
attributes = new LinkedHashMap<Attr,String>();
}
/**
* Creates an empty element with no initial indenting.
*/
HtmlElement() {
this(0);
}
/**
* Creates an element with a given indent, and a list of sub-elements.
* @param indent The amount to indent by.
* @param subElements a list of elements directly subordinate to this element.
*/
HtmlElement(int indent, HtmlElement... subElements) {
this(indent);
for (HtmlElement e: subElements) add(e);
}
/**
* Creates an empty element with no indenting.
* @param subElements a list of elements directly subordinate to this element.
*/
HtmlElement(HtmlElement... subElements) {
this(0, subElements);
}
/**
* Adds a sub element to the end of the list of sub elements for this element.
* @param elt The new sub element to be added.
* @return The current element.
*/
public HtmlElement add(HtmlElement elt) {
subElements.add(elt);
elt.setIndent(indent < 0 ? indent : indent + 1);
return this;
}
/**
* Adds an attribute to the set of attributes. This will replace any existing values
* for this attribute.
* @param attr The attribute to add.
* @param val The value for the attribute.
* @return The current element.
*/
public HtmlElement addAttr(Attr attr, Object val) {
attributes.put(attr, val.toString());
return this;
}
/**
* This method sends the given text to the output stream.
* For indented text, this is always called when a new line has just been started.
* The method always finishes while at the end of a line.
* @param out the writer to send the HTML to.
*/
public void sendTo(PrintWriter out) {
if (shouldIndent()) out.append(indentStr);
out.append('<').append(getTag());
for (Map.Entry<Attr,String> attr: attributes.entrySet()) out.append(' ').append(attr.getKey().toString()).append("=\"").append(attr.getValue()).append('\"');
if (subElements.isEmpty()) {
// no sub elements, so close the tag
out.append("/>");
} else {
out.append('>');
// iterate over the sub elements
for (HtmlElement e: subElements) {
if (e.shouldIndent()) out.append('\n');
e.sendTo(out);
}
if (C.last(subElements).shouldIndent()) out.append('\n').append(indentStr);
out.append("</").append(getTag()).append('>');
}
}
/**
* Tests if this element should be indented in the current output.
* @return <code>true</code> if indenting should occur.
*/
protected boolean shouldIndent() {
return indentStr != null;
}
/**
* Sets the indent of this element. Used by the parent.
* @param indent The new indent to use.
*/
private void setIndent(int indent) {
this.indent = indent;
if (indent < 0) {
indentStr = null;
for (HtmlElement e: subElements) e.setIndent(indent);
} else {
indentStr = generateIndent(indent);
for (HtmlElement e: subElements) e.setIndent(indent + 1);
}
}
/**
* Create a string of spaces for indenting.
* @param i The number of indents to use.
* @return A string of spaces, equal to i * {@link #INDENT_CHARS}, or <code>null</code>
* if i < 0.
*/
private static String generateIndent(int i) {
if (i < 0) return null;
StringBuilder s = new StringBuilder();
while (--i > 0) s.append(INDENT_CHARS);
return s.toString();
}
/**
* Any implementing classes must implement this method to create the text representation
* of the tag in the emitted HTML.
* @return The string for this element.
*/
protected abstract String getTag();
}