diff options
Diffstat (limited to 'java/com/google/gwtexpui/safehtml/client/SafeHtmlBuilder.java')
-rw-r--r-- | java/com/google/gwtexpui/safehtml/client/SafeHtmlBuilder.java | 425 |
1 files changed, 425 insertions, 0 deletions
diff --git a/java/com/google/gwtexpui/safehtml/client/SafeHtmlBuilder.java b/java/com/google/gwtexpui/safehtml/client/SafeHtmlBuilder.java new file mode 100644 index 0000000000..cde0f2af75 --- /dev/null +++ b/java/com/google/gwtexpui/safehtml/client/SafeHtmlBuilder.java @@ -0,0 +1,425 @@ +// 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. */ +public class SafeHtmlBuilder extends SafeHtml { + private static final long serialVersionUID = 1L; + + 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,'''); + }-*/; + } +} |