425 lines
11 KiB
Java
425 lines
11 KiB
Java
// Copyright (C) 2009 The Android Open Source Project
|
|
//
|
|
// 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 com.google.gwtexpui.safehtml.client;
|
|
|
|
import com.google.gwt.core.client.GWT;
|
|
|
|
/** Safely constructs a {@link SafeHtml}, escaping user provided content. */
|
|
@SuppressWarnings("serial")
|
|
public class SafeHtmlBuilder extends SafeHtml {
|
|
private static final Impl impl;
|
|
|
|
static {
|
|
if (GWT.isClient()) {
|
|
impl = new ClientImpl();
|
|
} else {
|
|
impl = new ServerImpl();
|
|
}
|
|
}
|
|
|
|
private final BufferDirect dBuf;
|
|
private Buffer cb;
|
|
|
|
private BufferSealElement sBuf;
|
|
private AttMap att;
|
|
|
|
public SafeHtmlBuilder() {
|
|
cb = dBuf = new BufferDirect();
|
|
}
|
|
|
|
/** @return true if this builder has not had an append occur yet. */
|
|
public boolean isEmpty() {
|
|
return dBuf.isEmpty();
|
|
}
|
|
|
|
/** @return true if this builder has content appended into it. */
|
|
public boolean hasContent() {
|
|
return !isEmpty();
|
|
}
|
|
|
|
public SafeHtmlBuilder append(boolean in) {
|
|
cb.append(in);
|
|
return this;
|
|
}
|
|
|
|
public SafeHtmlBuilder append(char in) {
|
|
switch (in) {
|
|
case '&':
|
|
cb.append("&");
|
|
break;
|
|
|
|
case '>':
|
|
cb.append(">");
|
|
break;
|
|
|
|
case '<':
|
|
cb.append("<");
|
|
break;
|
|
|
|
case '"':
|
|
cb.append(""");
|
|
break;
|
|
|
|
case '\'':
|
|
cb.append("'");
|
|
break;
|
|
|
|
default:
|
|
cb.append(in);
|
|
break;
|
|
}
|
|
return this;
|
|
}
|
|
|
|
public SafeHtmlBuilder append(int in) {
|
|
cb.append(in);
|
|
return this;
|
|
}
|
|
|
|
public SafeHtmlBuilder append(long in) {
|
|
cb.append(in);
|
|
return this;
|
|
}
|
|
|
|
public SafeHtmlBuilder append(float in) {
|
|
cb.append(in);
|
|
return this;
|
|
}
|
|
|
|
public SafeHtmlBuilder append(double in) {
|
|
cb.append(in);
|
|
return this;
|
|
}
|
|
|
|
/** Append already safe HTML as-is, avoiding double escaping. */
|
|
public SafeHtmlBuilder append(com.google.gwt.safehtml.shared.SafeHtml in) {
|
|
if (in != null) {
|
|
cb.append(in.asString());
|
|
}
|
|
return this;
|
|
}
|
|
|
|
/** Append already safe HTML as-is, avoiding double escaping. */
|
|
public SafeHtmlBuilder append(SafeHtml in) {
|
|
if (in != null) {
|
|
cb.append(in.asString());
|
|
}
|
|
return this;
|
|
}
|
|
|
|
/** Append the string, escaping unsafe characters. */
|
|
public SafeHtmlBuilder append(String in) {
|
|
if (in != null) {
|
|
impl.escapeStr(this, in);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
/** Append the string, escaping unsafe characters. */
|
|
public SafeHtmlBuilder append(StringBuilder in) {
|
|
if (in != null) {
|
|
append(in.toString());
|
|
}
|
|
return this;
|
|
}
|
|
|
|
/** Append the string, escaping unsafe characters. */
|
|
public SafeHtmlBuilder append(StringBuffer in) {
|
|
if (in != null) {
|
|
append(in.toString());
|
|
}
|
|
return this;
|
|
}
|
|
|
|
/** Append the result of toString(), escaping unsafe characters. */
|
|
public SafeHtmlBuilder append(Object in) {
|
|
if (in != null) {
|
|
append(in.toString());
|
|
}
|
|
return this;
|
|
}
|
|
|
|
/** Append the string, escaping unsafe characters. */
|
|
public SafeHtmlBuilder append(CharSequence in) {
|
|
if (in != null) {
|
|
escapeCS(this, in);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Open an element, appending "{@code <tagName>}" to the buffer.
|
|
*
|
|
* <p>After the element is open the attributes may be manipulated until the next {@code append},
|
|
* {@code openElement}, {@code closeSelf} or {@code closeElement} call.
|
|
*
|
|
* @param tagName name of the HTML element to open.
|
|
*/
|
|
public SafeHtmlBuilder openElement(String tagName) {
|
|
assert isElementName(tagName);
|
|
cb.append("<");
|
|
cb.append(tagName);
|
|
if (sBuf == null) {
|
|
att = new AttMap();
|
|
sBuf = new BufferSealElement(this);
|
|
}
|
|
att.reset(tagName);
|
|
cb = sBuf;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Get an attribute of the last opened element.
|
|
*
|
|
* @param name name of the attribute to read.
|
|
* @return the attribute value, as a string. The empty string if the attribute has not been
|
|
* assigned a value. The returned string is the raw (unescaped) value.
|
|
*/
|
|
public String getAttribute(String name) {
|
|
assert isAttributeName(name);
|
|
assert cb == sBuf;
|
|
return att.get(name);
|
|
}
|
|
|
|
/**
|
|
* Set an attribute of the last opened element.
|
|
*
|
|
* @param name name of the attribute to set.
|
|
* @param value value to assign; any existing value is replaced. The value is escaped (if
|
|
* necessary) during the assignment.
|
|
*/
|
|
public SafeHtmlBuilder setAttribute(String name, String value) {
|
|
assert isAttributeName(name);
|
|
assert cb == sBuf;
|
|
att.set(name, value != null ? value : "");
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Set an attribute of the last opened element.
|
|
*
|
|
* @param name name of the attribute to set.
|
|
* @param value value to assign, any existing value is replaced.
|
|
*/
|
|
public SafeHtmlBuilder setAttribute(String name, int value) {
|
|
return setAttribute(name, String.valueOf(value));
|
|
}
|
|
|
|
/**
|
|
* Append a new value into a whitespace delimited attribute.
|
|
*
|
|
* <p>If the attribute is not yet assigned, this method sets the attribute. If the attribute is
|
|
* already assigned, the new value is appended onto the end, after appending a single space to
|
|
* delimit the values.
|
|
*
|
|
* @param name name of the attribute to append onto.
|
|
* @param value additional value to append.
|
|
*/
|
|
public SafeHtmlBuilder appendAttribute(String name, String value) {
|
|
if (value != null && value.length() > 0) {
|
|
final String e = getAttribute(name);
|
|
return setAttribute(name, e.length() > 0 ? e + " " + value : value);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
/** Set the height attribute of the current element. */
|
|
public SafeHtmlBuilder setHeight(int height) {
|
|
return setAttribute("height", height);
|
|
}
|
|
|
|
/** Set the width attribute of the current element. */
|
|
public SafeHtmlBuilder setWidth(int width) {
|
|
return setAttribute("width", width);
|
|
}
|
|
|
|
/** Set the CSS class name for this element. */
|
|
public SafeHtmlBuilder setStyleName(String style) {
|
|
assert isCssName(style);
|
|
return setAttribute("class", style);
|
|
}
|
|
|
|
/**
|
|
* Add an additional CSS class name to this element.
|
|
*
|
|
* <p>If no CSS class name has been specified yet, this method initializes it to the single name.
|
|
*/
|
|
public SafeHtmlBuilder addStyleName(String style) {
|
|
assert isCssName(style);
|
|
return appendAttribute("class", style);
|
|
}
|
|
|
|
private void sealElement0() {
|
|
assert cb == sBuf;
|
|
cb = dBuf;
|
|
att.onto(cb, this);
|
|
}
|
|
|
|
Buffer sealElement() {
|
|
sealElement0();
|
|
cb.append(">");
|
|
return cb;
|
|
}
|
|
|
|
/** Close the current element with a self closing suffix ("/ >"). */
|
|
public SafeHtmlBuilder closeSelf() {
|
|
sealElement0();
|
|
cb.append(" />");
|
|
return this;
|
|
}
|
|
|
|
/** Append a closing tag for the named element. */
|
|
public SafeHtmlBuilder closeElement(String name) {
|
|
assert isElementName(name);
|
|
cb.append("</");
|
|
cb.append(name);
|
|
cb.append(">");
|
|
return this;
|
|
}
|
|
|
|
/** Append "&nbsp;" - a non-breaking space, useful in empty table cells. */
|
|
public SafeHtmlBuilder nbsp() {
|
|
cb.append(" ");
|
|
return this;
|
|
}
|
|
|
|
/** Append "<br />" - a line break with no attributes */
|
|
public SafeHtmlBuilder br() {
|
|
cb.append("<br />");
|
|
return this;
|
|
}
|
|
|
|
/** Append "<tr>"; attributes may be set if needed */
|
|
public SafeHtmlBuilder openTr() {
|
|
return openElement("tr");
|
|
}
|
|
|
|
/** Append "</tr>" */
|
|
public SafeHtmlBuilder closeTr() {
|
|
return closeElement("tr");
|
|
}
|
|
|
|
/** Append "<td>"; attributes may be set if needed */
|
|
public SafeHtmlBuilder openTd() {
|
|
return openElement("td");
|
|
}
|
|
|
|
/** Append "</td>" */
|
|
public SafeHtmlBuilder closeTd() {
|
|
return closeElement("td");
|
|
}
|
|
|
|
/** Append "<th>"; attributes may be set if needed */
|
|
public SafeHtmlBuilder openTh() {
|
|
return openElement("th");
|
|
}
|
|
|
|
/** Append "</th>" */
|
|
public SafeHtmlBuilder closeTh() {
|
|
return closeElement("th");
|
|
}
|
|
|
|
/** Append "<div>"; attributes may be set if needed */
|
|
public SafeHtmlBuilder openDiv() {
|
|
return openElement("div");
|
|
}
|
|
|
|
/** Append "</div>" */
|
|
public SafeHtmlBuilder closeDiv() {
|
|
return closeElement("div");
|
|
}
|
|
|
|
/** Append "<span>"; attributes may be set if needed */
|
|
public SafeHtmlBuilder openSpan() {
|
|
return openElement("span");
|
|
}
|
|
|
|
/** Append "</span>" */
|
|
public SafeHtmlBuilder closeSpan() {
|
|
return closeElement("span");
|
|
}
|
|
|
|
/** Append "<a>"; attributes may be set if needed */
|
|
public SafeHtmlBuilder openAnchor() {
|
|
return openElement("a");
|
|
}
|
|
|
|
/** Append "</a>" */
|
|
public SafeHtmlBuilder closeAnchor() {
|
|
return closeElement("a");
|
|
}
|
|
|
|
/** Append "<param name=... value=... />". */
|
|
public SafeHtmlBuilder paramElement(String name, String value) {
|
|
openElement("param");
|
|
setAttribute("name", name);
|
|
setAttribute("value", value);
|
|
return closeSelf();
|
|
}
|
|
|
|
/** @return an immutable {@link SafeHtml} representation of the buffer. */
|
|
public SafeHtml toSafeHtml() {
|
|
return new SafeHtmlString(asString());
|
|
}
|
|
|
|
@Override
|
|
public String asString() {
|
|
return cb.toString();
|
|
}
|
|
|
|
private static void escapeCS(SafeHtmlBuilder b, CharSequence in) {
|
|
for (int i = 0; i < in.length(); i++) {
|
|
b.append(in.charAt(i));
|
|
}
|
|
}
|
|
|
|
private static boolean isElementName(String name) {
|
|
return name.matches("^[a-zA-Z][a-zA-Z0-9_-]*$");
|
|
}
|
|
|
|
private static boolean isAttributeName(String name) {
|
|
return isElementName(name);
|
|
}
|
|
|
|
private static boolean isCssName(String name) {
|
|
return isElementName(name);
|
|
}
|
|
|
|
private abstract static class Impl {
|
|
abstract void escapeStr(SafeHtmlBuilder b, String in);
|
|
}
|
|
|
|
private static class ServerImpl extends Impl {
|
|
@Override
|
|
void escapeStr(SafeHtmlBuilder b, String in) {
|
|
SafeHtmlBuilder.escapeCS(b, in);
|
|
}
|
|
}
|
|
|
|
private static class ClientImpl extends Impl {
|
|
@Override
|
|
void escapeStr(SafeHtmlBuilder b, String in) {
|
|
b.cb.append(escape(in));
|
|
}
|
|
|
|
private static native String escape(String src) /*-{ return src.replace(/&/g,'&')
|
|
.replace(/>/g,'>')
|
|
.replace(/</g,'<')
|
|
.replace(/"/g,'"')
|
|
.replace(/'/g,''');
|
|
}-*/;
|
|
}
|
|
}
|