|  | // 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,'''); | 
|  | }-*/; | 
|  | } | 
|  | } |