001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: * $Header:$
018: */
019: package org.apache.beehive.netui.tags;
020:
021: import java.io.UnsupportedEncodingException;
022: import java.util.Iterator;
023: import java.util.Map;
024: import javax.servlet.jsp.JspException;
025:
026: import org.apache.beehive.netui.core.URLCodec;
027: import org.apache.beehive.netui.tags.rendering.AbstractRenderAppender;
028: import org.apache.beehive.netui.util.internal.InternalStringBuilder;
029:
030: /**
031: * This class provides a set of static methods that provide HTML utility code.
032: */
033: public class HtmlUtils {
034: /**
035: * Detect simple HTML contained inside of the given <code>value</code> string.
036: * @param value the value
037: * @return <code>true</code> if the string contains HTML; <code>false</code> otherwise.
038: */
039: public static boolean containsHtml(String value) {
040: int numChars = value.length();
041: char c;
042:
043: for (int i = 0; i < numChars; i++) {
044: c = value.charAt(i);
045: switch (c) {
046: case '<':
047: return true;
048: case '&':
049: return true;
050: case '"':
051: return true;
052: }
053: }
054: return false;
055: }
056:
057: /**
058: * Filter the specified value for characters that are sensitive to
059: * HTML interpreters. It will return a string with these characters replaced
060: * with HTML entities. This method calls the overloaded method with <code>markupHTMLSpaceReturn</code>
061: * set to <code>false</code>.
062: * @param value The <code>String</code> value to be filtered and returned.
063: * @param result the {@link AbstractRenderAppender} to which the results should be rendered
064: */
065: public static void filter(String value,
066: AbstractRenderAppender result) {
067: filter(value, result, false);
068: }
069:
070: /**
071: * Filter the specified string for characters that are sensitive to
072: * HTML interpreters, returning the string with these characters replaced
073: * by the corresponding character entities.
074: * @param value The <code>String</code> value to be filtered and returned.
075: * @param markupHTMLSpaceReturn convert space characters and return characters
076: * to &nbsp; and <br /> marketup for html.
077: * @param result the {@link AbstractRenderAppender} to which the results should be rendered
078: */
079: public static void filter(String value,
080: AbstractRenderAppender result, boolean markupHTMLSpaceReturn) {
081: // if the value is null, return
082: if (value == null)
083: return;
084:
085: // convert the string
086: int numChars = value.length();
087: char c;
088: char prev = 0;
089:
090: for (int i = 0; i < numChars; i++) {
091: c = value.charAt(i);
092: switch (c) {
093: case '<':
094: result.append("<");
095: break;
096: case '>':
097: result.append(">");
098: break;
099: case '&':
100: result.append("&");
101: break;
102: case '"':
103: result.append(""");
104: break;
105: case '\'':
106: result.append("'");
107: break;
108: case ' ':
109: if (markupHTMLSpaceReturn) {
110: if (prev == ' ') {
111: result.append(" ");
112: } else
113: result.append(c);
114: } else
115: result.append(c);
116: break;
117: case '\n':
118: if (markupHTMLSpaceReturn) {
119: result.append("<br />");
120: } else
121: result.append(c);
122: break;
123: default:
124: result.append(c);
125: }
126: prev = c;
127: }
128: }
129:
130: /**
131: * Escape the escapes (") and (\\) with escapes. These characters will be replaced with
132: * (") and (\\\\) respectively.
133: * @param value the string to escape
134: * @return the escaped string
135: */
136: public static String escapeEscapes(String value) {
137: assert (value != null);
138: InternalStringBuilder sb = new InternalStringBuilder(value
139: .length());
140: for (int i = 0; i < value.length(); i++) {
141: char c = value.charAt(i);
142: if (c == '"') {
143: sb.append(""");
144: continue;
145: }
146: if (c == '\\') {
147: sb.append("\\\\");
148: continue;
149: }
150: sb.append(c);
151: }
152: return sb.toString();
153: }
154:
155: /**
156: * Deprecated method using an old algorithm to escape escapes.
157: * @param value the value
158: * @return the escaped value
159: * @deprecated use {@link #escapeEscapes(String)} instead
160: */
161: public static String legacyEscapeEscapes(String value) {
162: assert (value != null);
163: InternalStringBuilder sb = new InternalStringBuilder(value
164: .length());
165: for (int i = 0; i < value.length(); i++) {
166: char c = value.charAt(i);
167: if (c == '"') {
168: sb.append("\\\"");
169: continue;
170: }
171: if (c == '\\') {
172: sb.append("\\\\");
173: continue;
174: }
175: sb.append(c);
176: }
177: return sb.toString();
178: }
179:
180: /**
181: * Add parameters contained in the givem {@link Map} to the URL. This method can handle values of type
182: * {@link String}, String array, null, and {@link Object} contained in the {@link Map}. If the type
183: * is an array, each of the items in the array will be added in-order onto the URL. {@link Object}s in the
184: * array will be added to the URL after calling {@link Object#toString()}.
185: *
186: * @param url the base URL
187: * @param params the parameters to add to the URL
188: * @param encoding the encoding to use when encoding parameters onto the URL
189: * @return the URL created by adding the parameters onto the base URL string
190: * @throws JspException
191: */
192: public static String addParams(String url, Map params,
193: String encoding) throws JspException {
194: InternalStringBuilder urlBuffer = new InternalStringBuilder(url);
195:
196: try {
197: // Add dynamic parameters if requested
198: if ((params != null) && (params.size() > 0)) {
199:
200: // Add the required request parameters
201: boolean question = url.indexOf('?') >= 0;
202: Iterator keys = params.keySet().iterator();
203: while (keys.hasNext()) {
204: String key = (String) keys.next();
205: Object value = params.get(key);
206: if (value == null) {
207: if (!question) {
208: urlBuffer.append('?');
209: question = true;
210: } else {
211: urlBuffer.append("&");
212: }
213: urlBuffer
214: .append(URLCodec.encode(key, encoding));
215: urlBuffer.append('='); // Interpret null as "no value"
216: } else if (value instanceof String) {
217: if (!question) {
218: urlBuffer.append('?');
219: question = true;
220: } else {
221: urlBuffer.append("&");
222: }
223: urlBuffer
224: .append(URLCodec.encode(key, encoding));
225: urlBuffer.append('=');
226: urlBuffer.append(URLCodec.encode(
227: (String) value, encoding));
228: } else if (value instanceof String[]) {
229: String values[] = (String[]) value;
230: for (int i = 0; i < values.length; i++) {
231: if (!question) {
232: urlBuffer.append('?');
233: question = true;
234: } else {
235: urlBuffer.append("&");
236: }
237: urlBuffer.append(URLCodec.encode(key,
238: encoding));
239: urlBuffer.append('=');
240: urlBuffer.append(URLCodec.encode(values[i],
241: encoding));
242: }
243: }
244: /* convert all other objects to String */
245: else {
246: if (!question) {
247: urlBuffer.append('?');
248: question = true;
249: } else {
250: urlBuffer.append("&");
251: }
252: urlBuffer
253: .append(URLCodec.encode(key, encoding));
254: urlBuffer.append('=');
255: urlBuffer.append(URLCodec.encode(value
256: .toString(), encoding));
257: }
258: }
259: }
260: } catch (UnsupportedEncodingException uee) {
261: JspException jspException = new JspException(
262: "Unsupported Encoding" + encoding, uee);
263:
264: // todo: future cleanup
265: // The 2.5 Servlet api will set the initCause in the Throwable superclass during construction,
266: // this will cause an IllegalStateException on the following call.
267: if (jspException.getCause() == null) {
268: jspException.initCause(uee);
269: }
270: throw jspException;
271: }
272:
273: return urlBuffer.toString();
274: }
275:
276: /**
277: * Determine if the given <code>value</code> contains an HTML entity.
278: * @param value the value to check for an entity
279: * @return <code>true</code> if the value contains an entity; <code>false</code> otherwise.
280: */
281: public static boolean containsEntity(String value) {
282: assert (value != null) : "Parameter 'value' must not be null";
283:
284: int pos = value.indexOf('&');
285: if (pos == -1)
286: return false;
287:
288: int end = value.indexOf(';');
289: if (end != -1 && pos < end) {
290: // extract the entity and then verify it is
291: // a valid unicode identifier.
292: String entity = value.substring(pos + 1, end);
293: if (entity.length() == 0)
294: return false;
295: char[] chars = entity.toCharArray();
296:
297: // verify the start is an indentifier start
298: // and the rest is a part.
299: if (!Character.isUnicodeIdentifierStart(chars[0])) {
300: if (chars[0] == '#' && chars.length > 1) {
301: for (int i = 1; i < chars.length; i++) {
302: if (!Character.isDigit(chars[i]))
303: return false;
304: }
305: return true;
306: }
307: return false;
308: }
309: for (int i = 1; i < chars.length; i++) {
310: if (!Character.isUnicodeIdentifierPart(chars[i]))
311: return false;
312: }
313: // good indentifier
314: return true;
315: }
316: return false;
317: }
318: }
|