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