View Javadoc

1   /*
2    * FCKeditor - The text editor for Internet - http://www.fckeditor.net
3    * Copyright (C) 2004-2010 Frederico Caldeira Knabben
4    * 
5    * == BEGIN LICENSE ==
6    * 
7    * Licensed under the terms of any of the following licenses at your
8    * choice:
9    * 
10   *  - GNU General Public License Version 2 or later (the "GPL")
11   *    http://www.gnu.org/licenses/gpl.html
12   * 
13   *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
14   *    http://www.gnu.org/licenses/lgpl.html
15   * 
16   *  - Mozilla Public License Version 1.1 or later (the "MPL")
17   *    http://www.mozilla.org/MPL/MPL-1.1.html
18   * 
19   * == END LICENSE ==
20   */
21  package net.fckeditor;
22  
23  import javax.servlet.http.HttpServletRequest;
24  
25  import net.fckeditor.handlers.PropertiesLoader;
26  import net.fckeditor.tool.Compatibility;
27  import net.fckeditor.tool.Utils;
28  import net.fckeditor.tool.XHtmlTagTool;
29  
30  /**
31   * Java representation of the <a href="http://docs.fckeditor.net/FCKeditor_2.x/Developers_Guide/Server_Side_Integration#FCKeditor_Creator"
32   * target="_blank">FCKeditor</a>. This representation reflects the editor in an
33   * object-oriented way. It can be configured as any other JavaBean type class.
34   * The final output of this class is HTML code.<br />
35   * <strong>Note:</strong> It's your responsibility to supply reasonable and
36   * valid values, only {@code request}, {@code instanceName} and {@code inputName}
37   * will be checked for sanity.
38   * 
39   * @version $Id: FCKeditor.java 4785 2009-12-21 20:10:28Z mosipov $
40   */
41  public class FCKeditor {
42  
43  	private FCKeditorConfig fckConfig = new FCKeditorConfig();
44  	private String instanceName;
45  	private String inputName;
46  	private HttpServletRequest request;
47  
48  	// defaults
49  	private String value = Utils.EMPTY_STRING;
50  	private String toolbarSet = PropertiesLoader.getEditorToolbarSet();
51  	private String width = PropertiesLoader.getEditorWidth();
52  	private String height = PropertiesLoader.getEditorHeight();
53  	private String basePath = PropertiesLoader.getEditorBasePath();
54  
55  	/**
56  	 * Class constructor with all parameters.
57  	 * 
58  	 * @param request
59  	 *            current user request instance
60  	 * @param instanceName
61  	 *            the unique name of this editor
62  	 * @param inputName
63  	 *            the name for the underlying input element. See
64  	 *            {@link #setInputName(String)} for details.
65  	 * @param width
66  	 *            the desired editor width (CSS-style value)
67  	 * @param height
68  	 *            the desired editor height (CSS-style value)
69  	 * @param toolbarSet
70  	 *            the desired toolbar set name
71  	 * @param value
72  	 *            the HTML markup of this editor. Markup will be properly
73  	 *            escaped.
74  	 * @param basePath
75  	 *            the base path of this editor, absolute to the context
76  	 * @throws IllegalArgumentException
77  	 *             if instanceName is empty or not a valid XHTML id
78  	 */
79  	public FCKeditor(HttpServletRequest request, String instanceName,
80  			String inputName, String width, String height, String toolbarSet,
81  			String value, String basePath) {
82  		
83  		this(request, instanceName, inputName);
84  		this.width = width;
85  		this.height = height;
86  		this.toolbarSet = toolbarSet;
87  		this.value = value;
88  		this.basePath = basePath;
89  		
90  	}
91  
92  	/**
93  	 * Class constructor with a extended set of parameters.
94  	 * 
95  	 * @param request
96  	 *            current user request instance
97  	 * @param instanceName
98  	 *            the unique name of this editor
99  	 * @param width
100 	 *            the desired editor width (CSS-style value)
101 	 * @param height
102 	 *            the desired editor height (CSS-style value)
103 	 * @param toolbarSet
104 	 *            the desired toolbar set name
105 	 * @param value
106 	 *            the HTML markup of this editor. Markup will be properly
107 	 *            escaped.
108 	 * @param basePath
109 	 *            the base path of this editor, absolute to the context
110 	 * @throws IllegalArgumentException
111 	 *             if instanceName is empty or not a valid XHTML id
112 	 */
113 	public FCKeditor(final HttpServletRequest request,
114 			final String instanceName, final String width, final String height,
115 			final String toolbarSet, final String value, final String basePath) {
116 
117 		this(request, instanceName, null, width, height, toolbarSet, value,
118 				basePath);
119 
120 	}
121 
122 	/**
123 	 * Class constructor with a minimal set of parameters.
124 	 * 
125 	 * Omitted parameters will be set to default values.
126 	 * 
127 	 * @param request
128 	 *            current user request instance
129 	 * @param instanceName
130 	 *            the unique name of this editor
131 	 * @throws IllegalArgumentException
132 	 *             if instanceName is empty or not a valid HTML id
133 	 */
134 	public FCKeditor(final HttpServletRequest request, final String instanceName) {
135 
136 		if (request == null)
137 			throw new NullPointerException("the request cannot be null");
138 		this.request = request;
139 
140 		setInstanceName(instanceName);
141 
142 	}
143 
144 	/**
145 	 * Class constructor with a basic set of parameters.
146 	 * 
147 	 * Omitted parameters will be set to default values.
148 	 * 
149 	 * @param request
150 	 *            current user request instance
151 	 * @param instanceName
152 	 *            the unique name of this editor
153 	 * @param inputName
154 	 *            the name for the underlying input element. See
155 	 *            {@link #setInputName(String)} for details.
156 	 * @throws IllegalArgumentException
157 	 *             if instanceName is empty or not a valid HTML id
158 	 */
159 	public FCKeditor(HttpServletRequest request, String instanceName,
160 			String inputName) {
161 		this(request, instanceName);
162 		setInputName(inputName);
163 	}
164 
165 	/**
166 	 * Sets the unique name of this editor.
167 	 * 
168 	 * @param instanceName
169 	 *            the unique name of this editor
170 	 * @throws IllegalArgumentException
171 	 *             if instanceName is empty or not a valid XHTML id
172 	 */
173 	public void setInstanceName(final String instanceName) {
174 		if (Utils.isEmpty(instanceName))
175 			throw new IllegalArgumentException("instanceName cannot be empty");
176 		if (!instanceName.matches("\\p{Alpha}[\\p{Alnum}:_.-]*"))
177 			throw new IllegalArgumentException(
178 					"instanceName must be a valid XHTML id containing only \"\\p{Alpha}[\\p{Alnum}:_.-]*\"");
179 		this.instanceName = instanceName;
180 	}
181 	
182 	/**
183 	 * Sets the name for the underlying input element. Empty strings will be
184 	 * ignored and field will be reset to {@code instanceName}.
185 	 * 
186 	 * @param inputName
187 	 *            the name for the underlying input element
188 	 */
189 	public void setInputName(final String inputName) {
190 		if (Utils.isEmpty(inputName))
191 			this.inputName = instanceName;
192 		else
193 			this.inputName = inputName;
194 	}
195 
196 	/**
197 	 * Sets the initial value to be edited as HTML markup.
198 	 * 
199 	 * @param value
200 	 *            the HTML markup of this editor. Markup will be properly
201 	 *            escaped.
202 	 */
203 	public void setValue(final String value) {
204 		this.value = value;
205 	}
206 
207 	/**
208 	 * Sets the base path of this editor. The base path reflects the location of
209 	 * the editor files absolute to the context root <i>not</i> the server root.
210 	 * 
211 	 * @param basePath
212 	 *            the base path of this editor, absolute to the context
213 	 */
214 	public void setBasePath(final String basePath) {
215 		this.basePath = basePath;
216 	}
217 
218 	/**
219 	 * Sets the name of the toolbar set of this editor.
220 	 * 
221 	 * @param toolbarSet
222 	 *            the desired toolbar set name
223 	 */
224 	public void setToolbarSet(final String toolbarSet) {
225 		this.toolbarSet = toolbarSet;
226 	}
227 
228 	/**
229 	 * Sets the width of this editor. This value can be any valid CSS width
230 	 * value.
231 	 * 
232 	 * @param width
233 	 *            the desired editor width (CSS-style value)
234 	 */
235 	public void setWidth(final String width) {
236 		this.width = width;
237 	}
238 
239 	/**
240 	 * Sets the height of this editor. This value can be any valid CSS height
241 	 * value.
242 	 * 
243 	 * @param height
244 	 *            the desired editor height (CSS-style value)
245 	 */
246 	public void setHeight(final String height) {
247 		this.height = height;
248 	}
249 
250 	/**
251 	 * Returns a configuration option. See {@link FCKeditorConfig} for more
252 	 * details.
253 	 * 
254 	 * @param name
255 	 *            the name of the parameter (case-sensitive)
256 	 * @return the value represented by this parameter, else null
257 	 */
258 	public String getConfig(String name) {
259 		return fckConfig.get(name);
260 	}
261 
262 	/**
263 	 * Sets a configuration option. See {@link FCKeditorConfig} for more
264 	 * details.
265 	 * 
266 	 * @param name
267 	 *            the name of the config option (case-sensitive)
268 	 * @param value
269 	 *            the value of the config option. Null values will be ignored.
270 	 */
271 	public void setConfig(String name, String value) {
272 		if (value != null)
273 			fckConfig.put(name, value);
274 	}
275 
276 	/**
277 	 * Escapes base XML entities as specified <a
278 	 * href="http://en.wikipedia.org/wiki/Xml#Entity_references">here</a>.
279 	 * 
280 	 * @param str
281 	 *            string to escape, empty strings will be ignored
282 	 * @return escaped string
283 	 */
284 	private String escapeXml(String str) {
285 
286 		if (Utils.isEmpty(str))
287 			return str;
288 
289 		StringBuffer sb = new StringBuffer();
290 
291 		int len = str.length();
292 		char c;
293 
294 		for (int i = 0; i < len; i++) {
295 
296 			c = str.charAt(i);
297 			switch (c) {
298 			case '&':
299 				sb.append("&amp;");
300 				break;
301 			case '<':
302 				sb.append("&lt;");
303 				break;
304 			case '>':
305 				sb.append("&gt;");
306 				break;
307 			case '"':
308 				sb.append("&quot;");
309 				break;
310 			// XML actually defines &apos; as entity for the apostrophe but we
311 			// user rather the numerical reference to avoid XHTML 1.0 validation
312 			// problems
313 			case '\'':
314 				sb.append("&#39;");
315 				break;
316 			default:
317 				sb.append(c);
318 				break;
319 			}
320 		}
321 
322 		return sb.toString();
323 	}
324 
325 	/**
326 	 * Creates the HTML representation of this editor instance. First of all,
327 	 * this method determines whether the request browser is supported.
328 	 * According to the result an appropriate HTML representation is assembled
329 	 * and returned.
330 	 * 
331 	 * @return HTML representation of this editor instance
332 	 */
333 	@Override
334 	public String toString() {
335 		StringBuffer strEditor = new StringBuffer();
336 
337 		strEditor.append("<div>");
338 		String encodedValue = escapeXml(value);
339 		
340 		/*
341 		 * We have to reset inputName to make sure that a potentially reassigned
342 		 * instanceName has been propagated to empty field.
343 		 */
344 		setInputName(inputName);
345 
346 		if (Compatibility.isCompatibleBrowser(request)) {
347 			strEditor.append(createInputForVariable(instanceName, inputName,
348 					encodedValue));
349 
350 			// create config HTML
351 			String configStr = fckConfig.getUrlParams();
352 			if (Utils.isNotEmpty(configStr))
353 				strEditor.append(createInputForVariable(instanceName
354 						.concat("___Config"), null, configStr));
355 
356 			// create IFrame
357 			StringBuffer editorLink = new StringBuffer(request.getContextPath());
358 			editorLink.append(basePath);
359 			editorLink.append("/editor/fckeditor.html?InstanceName=").append(
360 					instanceName);
361 			if (Utils.isNotEmpty(toolbarSet))
362 				editorLink.append("&amp;Toolbar=").append(toolbarSet);
363 
364 			XHtmlTagTool iframeTag = new XHtmlTagTool("iframe",
365 					XHtmlTagTool.SPACE);
366 			iframeTag.addAttribute("id", instanceName.concat("___Frame"));
367 			iframeTag.addAttribute("src", editorLink.toString());
368 			iframeTag.addAttribute("width", width);
369 			iframeTag.addAttribute("height", height);
370 			iframeTag.addAttribute("frameborder", "0");
371 			iframeTag.addAttribute("scrolling", "no");
372 			strEditor.append(iframeTag);
373 
374 		} else {
375 			XHtmlTagTool textareaTag = new XHtmlTagTool("textarea",
376 					encodedValue);
377 			textareaTag.addAttribute("name", inputName);
378 			textareaTag.addAttribute("rows", "4");
379 			textareaTag.addAttribute("cols", "40");
380 			textareaTag.addAttribute("wrap", "virtual");
381 			textareaTag.addAttribute("style", "width: ".concat(width).concat(
382 					"; height: ").concat(height));
383 		}
384 		strEditor.append("</div>");
385 		return strEditor.toString();
386 	}
387 
388 	/**
389 	 * Creates the HTML representation of this editor instance.
390 	 * 
391 	 * @see #toString()
392 	 * @return HTML representation of this editor instance
393 	 */
394 	public String createHtml() {
395 		return toString();
396 	}
397 
398 	/**
399 	 * Creates a hidden input element for the given attributes.
400 	 * @param id
401 	 *            id attribute of the input tag
402 	 * @param name
403 	 *            name attribute of the input tag
404 	 * @param value
405 	 *            value attribute of the input tag
406 	 * 
407 	 * @return the produced XHTML tag
408 	 */
409 	private String createInputForVariable(final String id, final String name,
410 			final String value) {
411 		XHtmlTagTool tag = new XHtmlTagTool("input");
412 		tag.addAttribute("id", id);
413 		tag.addAttribute("name", name);
414 		tag.addAttribute("value", value);
415 		tag.addAttribute("type", "hidden");
416 		return tag.toString();
417 	}
418 }