1   /**
2    * Copyright (c) 2000-2009 Liferay, Inc. All rights reserved.
3    *
4    * Permission is hereby granted, free of charge, to any person obtaining a copy
5    * of this software and associated documentation files (the "Software"), to deal
6    * in the Software without restriction, including without limitation the rights
7    * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8    * copies of the Software, and to permit persons to whom the Software is
9    * furnished to do so, subject to the following conditions:
10   *
11   * The above copyright notice and this permission notice shall be included in
12   * all copies or substantial portions of the Software.
13   *
14   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20   * SOFTWARE.
21   */
22  
23  package com.liferay.portlet.messageboards.util;
24  
25  import com.liferay.portal.kernel.log.Log;
26  import com.liferay.portal.kernel.log.LogFactoryUtil;
27  import com.liferay.portal.kernel.util.GetterUtil;
28  import com.liferay.portal.kernel.util.HtmlUtil;
29  import com.liferay.portal.kernel.util.StringPool;
30  import com.liferay.portal.kernel.util.StringUtil;
31  import com.liferay.portlet.messageboards.model.MBMessage;
32  
33  import java.util.ArrayList;
34  import java.util.HashMap;
35  import java.util.List;
36  import java.util.Map;
37  
38  /**
39   * <a href="BBCodeUtil.java.html"><b><i>View Source</i></b></a>
40   *
41   * @author Alexander Chow
42   *
43   */
44  public class BBCodeUtil {
45  
46      static Map<Integer, String> fontSizes = new HashMap<Integer, String>();
47  
48      static Map<String, String> listStyles = new HashMap<String, String>();
49  
50      static String[][] emoticons = {
51          {"angry.gif", ":angry:"},
52          {"bashful.gif", ":bashful:"},
53          {"big_grin.gif", ":grin:"},
54          {"blink.gif", ":blink:"},
55          {"blush.gif", ":*)"},
56          {"bored.gif", ":bored:"},
57          {"closed_eyes.gif", "-_-"},
58          {"cold.gif", ":cold:"},
59          {"cool.gif", "B)"},
60          {"darth_vader.gif", ":vader:"},
61          {"dry.gif", "<_<"},
62          {"exclamation.gif", ":what:"},
63          {"girl.gif", ":girl:"},
64          {"glare.gif", ">_>"},
65          {"happy.gif", ":)"},
66          {"huh.gif", ":huh:"},
67          {"in_love.gif", "<3"},
68          {"karate_kid.gif", ":kid:"},
69          {"kiss.gif", ":#"},
70          {"laugh.gif", ":lol:"},
71          {"mad.gif", ":mad:"},
72          {"mellow.gif", ":mellow:"},
73          {"ninja.gif", ":ph34r:"},
74          {"oh_my.gif", ":O"},
75          {"pac_man.gif", ":V"},
76          {"roll_eyes.gif", ":rolleyes:"},
77          {"sad.gif", ":("},
78          {"sleep.gif", ":sleep:"},
79          {"smile.gif", ":D"},
80          {"smug.gif", ":smug:"},
81          {"suspicious.gif", "8o"},
82          {"tongue.gif", ":P"},
83          {"unsure.gif", ":unsure:"},
84          {"wacko.gif", ":wacko:"},
85          {"wink.gif", ":wink:"},
86          {"wub.gif", ":wub:"}
87      };
88  
89      static {
90          fontSizes.put(new Integer(1), "<span style='font-size: 0.7em';>");
91          fontSizes.put(new Integer(2), "<span style='font-size: 0.8em';>");
92          fontSizes.put(new Integer(3), "<span style='font-size: 0.9em';>");
93          fontSizes.put(new Integer(4), "<span style='font-size: 1.0em';>");
94          fontSizes.put(new Integer(5), "<span style='font-size: 1.1em';>");
95          fontSizes.put(new Integer(6), "<span style='font-size: 1.3em';>");
96          fontSizes.put(new Integer(7), "<span style='font-size: 1.5em';>");
97  
98          listStyles.put("1", "<ol style='list-style-type: decimal';>");
99          listStyles.put("i", "<ol style='list-style-type: lower-roman';>");
100         listStyles.put("I", "<ol style='list-style-type: upper-roman';>");
101         listStyles.put("a", "<ol style='list-style-type: lower-alpha';>");
102         listStyles.put("A", "<ol style='list-style-type: upper-alpha';>");
103 
104         for (int i = 0; i < emoticons.length; i++) {
105             String[] emoticon = emoticons[i];
106 
107             String image = emoticon[0];
108             String code = emoticon[1];
109 
110             emoticon[0] =
111                 "<img alt='emoticon' src='@theme_images_path@/emoticons/" +
112                     image + "' />";
113             emoticon[1] = HtmlUtil.escape(code);
114         }
115     }
116 
117     public static final String[][] EMOTICONS = emoticons;
118 
119     public static String getHTML(MBMessage message) {
120         String body = message.getBody();
121 
122         try {
123             body = getHTML(body);
124         }
125         catch (Exception e) {
126             _log.error(
127                 "Could not parse message " + message.getMessageId() + " " +
128                     e.getMessage());
129         }
130 
131         return body;
132     }
133 
134     public static String getHTML(String bbcode) {
135         String html = HtmlUtil.escape(bbcode);
136 
137         html = StringUtil.replace(html, _BBCODE_TAGS, _HTML_TAGS);
138 
139         for (int i = 0; i < emoticons.length; i++) {
140             String[] emoticon = emoticons[i];
141 
142             html = StringUtil.replace(html, emoticon[1], emoticon[0]);
143         }
144 
145         BBCodeTag tag = null;
146 
147         StringBuilder sb = null;
148 
149         while ((tag = getFirstTag(html, "code")) != null) {
150             String preTag = html.substring(0, tag.getStartPos());
151             String postTag = html.substring(tag.getEndPos());
152 
153             String code = tag.getElement().replaceAll(
154                 "\t", StringPool.FOUR_SPACES);
155             String[] lines = code.split("\\n");
156             int digits = String.valueOf(lines.length + 1).length();
157 
158             sb = new StringBuilder(preTag);
159 
160             sb.append("<div class='code'>");
161 
162             for (int i = 0; i < lines.length; i++) {
163                 String index = String.valueOf(i + 1);
164                 int ld = index.length();
165 
166                 sb.append("<span class='code-lines'>");
167 
168                 for (int j = 0; j < digits - ld; j++) {
169                     sb.append("&nbsp;");
170                 }
171 
172                 lines[i] = StringUtil.replace(lines[i], "   ",
173                     StringPool.NBSP + StringPool.SPACE + StringPool.NBSP);
174                 lines[i] = StringUtil.replace(lines[i], "  ",
175                     StringPool.NBSP + StringPool.SPACE);
176 
177                 sb.append(index + "</span>");
178                 sb.append(lines[i]);
179 
180                 if (index.length() < lines.length) {
181                     sb.append("<br />");
182                 }
183             }
184 
185             sb.append("</div>");
186             sb.append(postTag);
187 
188             html = sb.toString();
189         }
190 
191         while ((tag = getFirstTag(html, "color")) != null) {
192             String preTag = html.substring(0, tag.getStartPos());
193             String postTag = html.substring(tag.getEndPos());
194 
195             sb = new StringBuilder(preTag);
196 
197             if (tag.hasParameter()) {
198                 sb.append("<span style='color: ");
199                 sb.append(tag.getParameter() + ";'>");
200                 sb.append(tag.getElement() + "</span>");
201             }
202             else {
203                 sb.append(tag.getElement());
204             }
205 
206             sb.append(postTag);
207 
208             html = sb.toString();
209         }
210 
211         while ((tag = getFirstTag(html, "email")) != null) {
212             String preTag = html.substring(0, tag.getStartPos());
213             String postTag = html.substring(tag.getEndPos());
214 
215             String mailto = GetterUtil.getString(
216                 tag.getParameter(), tag.getElement().trim());
217 
218             sb = new StringBuilder(preTag);
219 
220             sb.append("<a href='mailto: " + mailto + "'>");
221             sb.append(tag.getElement() + "</a>");
222             sb.append(postTag);
223 
224             html = sb.toString();
225         }
226 
227         while ((tag = getFirstTag(html, "font")) != null) {
228             String preTag = html.substring(0, tag.getStartPos());
229             String postTag = html.substring(tag.getEndPos());
230 
231             sb = new StringBuilder(preTag);
232 
233             if (tag.hasParameter()) {
234                 sb.append("<span style='font-family: ");
235                 sb.append(tag.getParameter() + "';>");
236                 sb.append(tag.getElement() + "</span>");
237             }
238             else {
239                 sb.append(tag.getElement());
240             }
241 
242             sb.append(postTag);
243 
244             html = sb.toString();
245         }
246 
247         while ((tag = getFirstTag(html, "img")) != null) {
248             String preTag = html.substring(0, tag.getStartPos());
249             String postTag = html.substring(tag.getEndPos());
250 
251             sb = new StringBuilder(preTag);
252 
253             sb.append("<img alt='' src='" + tag.getElement().trim() + "' />");
254             sb.append(postTag);
255 
256             html = sb.toString();
257         }
258 
259         while ((tag = getFirstTag(html, "list")) != null) {
260             String preTag = html.substring(0, tag.getStartPos());
261             String postTag = html.substring(tag.getEndPos());
262 
263             String[] items = _getListItems(tag.getElement());
264 
265             sb = new StringBuilder(preTag);
266 
267             if (tag.hasParameter() &&
268                 listStyles.containsKey(tag.getParameter())) {
269 
270                 sb.append(listStyles.get(tag.getParameter()));
271 
272                 for (int i = 0; i < items.length; i++) {
273                     if (items[i].trim().length() > 0) {
274                         sb.append("<li>" + items[i].trim() + "</li>");
275                     }
276                 }
277 
278                 sb.append("</ol>");
279             }
280             else {
281                 sb.append("<ul style='list-style-type: disc';>");
282 
283                 for (int i = 0; i < items.length; i++) {
284                     if (items[i].trim().length() > 0) {
285                         sb.append("<li>" + items[i].trim() + "</li>");
286                     }
287                 }
288 
289                 sb.append("</ul>");
290             }
291 
292             sb.append(postTag);
293 
294             html = sb.toString();
295         }
296 
297         while ((tag = getFirstTag(html, "quote")) != null) {
298             String preTag = html.substring(0, tag.getStartPos());
299             String postTag = html.substring(tag.getEndPos());
300 
301             sb = new StringBuilder(preTag);
302 
303             if (tag.hasParameter()) {
304                 sb.append("<div class='quote-title'>");
305                 sb.append(tag.getParameter() + ":</div>");
306             }
307 
308             sb.append("<div class='quote'>");
309             sb.append("<div class='quote-content'>");
310             sb.append(tag.getElement());
311             sb.append("</div></div>");
312             sb.append(postTag);
313 
314             html = sb.toString();
315         }
316 
317         while ((tag = getFirstTag(html, "size")) != null) {
318             String preTag = html.substring(0, tag.getStartPos());
319             String postTag = html.substring(tag.getEndPos());
320 
321             sb = new StringBuilder(preTag);
322 
323             if (tag.hasParameter()) {
324                 Integer size = new Integer(
325                     GetterUtil.getInteger(tag.getParameter()));
326 
327                 if (size.intValue() > 7) {
328                     size = new Integer(7);
329                 }
330 
331                 if (fontSizes.containsKey(size)) {
332                     sb.append(fontSizes.get(size));
333                     sb.append(tag.getElement() + "</span>");
334                 }
335                 else {
336                     sb.append(tag.getElement());
337                 }
338             }
339             else {
340                 sb.append(tag.getElement());
341             }
342 
343             sb.append(postTag);
344 
345             html = sb.toString();
346         }
347 
348         while ((tag = getFirstTag(html, "url")) != null) {
349             String preTag = html.substring(0, tag.getStartPos());
350             String postTag = html.substring(tag.getEndPos());
351 
352             String url = GetterUtil.getString(
353                 tag.getParameter(), tag.getElement().trim());
354 
355             sb = new StringBuilder(preTag);
356 
357             sb.append("<a href='" + url + "'>");
358             sb.append(tag.getElement() + "</a>");
359             sb.append(postTag);
360 
361             html = sb.toString();
362         }
363 
364         html = StringUtil.replace(html, "\n", "<br />");
365 
366         return html;
367     }
368 
369     public static BBCodeTag getFirstTag(String bbcode, String name) {
370         BBCodeTag tag = new BBCodeTag();
371 
372         String begTag = "[" + name;
373         String endTag = "[/" + name + "]";
374 
375         String preTag = StringUtil.extractFirst(bbcode, begTag);
376 
377         if (preTag == null) {
378             return null;
379         }
380 
381         if (preTag.length() != bbcode.length()) {
382             tag.setStartPos(preTag.length());
383 
384             String remainder = bbcode.substring(
385                 preTag.length() + begTag.length());
386 
387             int cb = remainder.indexOf("]");
388             int end = _getEndTagPos(remainder, begTag, endTag);
389 
390             if (cb > 0 && remainder.startsWith("=")) {
391                 tag.setParameter(remainder.substring(1, cb));
392                 tag.setElement(remainder.substring(cb + 1, end));
393             }
394             else if (cb == 0) {
395                 try {
396                     tag.setElement(remainder.substring(1, end));
397                 }
398                 catch (StringIndexOutOfBoundsException sioobe) {
399                     _log.error(bbcode);
400 
401                     throw sioobe;
402                 }
403             }
404         }
405 
406         if (tag.hasElement()) {
407             int length =
408                 begTag.length() + 1 + tag.getElement().length() +
409                     endTag.length();
410 
411             if (tag.hasParameter()) {
412                 length += 1 + tag.getParameter().length();
413             }
414 
415             tag.setEndPos(tag.getStartPos() + length);
416 
417             return tag;
418         }
419 
420         return null;
421     }
422 
423     private static int _getEndTagPos(
424         String remainder, String begTag, String endTag) {
425 
426         int nextBegTagPos = remainder.indexOf(begTag);
427         int nextEndTagPos = remainder.indexOf(endTag);
428 
429         while ((nextBegTagPos < nextEndTagPos) && (nextBegTagPos >= 0)) {
430             nextBegTagPos = remainder.indexOf(
431                 begTag, nextBegTagPos + begTag.length());
432             nextEndTagPos = remainder.indexOf(
433                 endTag, nextEndTagPos + endTag.length());
434         }
435 
436         return nextEndTagPos;
437     }
438 
439     private static String[] _getListItems(String tagElement) {
440         List<String> items = new ArrayList<String>();
441 
442         StringBuilder sb = new StringBuilder();
443 
444         int nestLevel = 0;
445 
446         for (String item : StringUtil.split(tagElement, "[*]")) {
447             item = item.trim();
448 
449             if (item.length() == 0) {
450                 continue;
451             }
452 
453             int begTagCount = StringUtil.count(item, "[list");
454 
455             if (begTagCount > 0) {
456                 nestLevel += begTagCount;
457             }
458 
459             int endTagCount = StringUtil.count(item, "[/list]");
460 
461             if (endTagCount > 0) {
462                 nestLevel -= endTagCount;
463             }
464 
465             if (nestLevel == 0) {
466                 if ((begTagCount == 0) && (endTagCount == 0)) {
467                     items.add(item);
468                 }
469                 else if (endTagCount > 0) {
470                     if (sb.length() > 0) {
471                         sb.append("[*]");
472                     }
473 
474                     sb.append(item);
475 
476                     items.add(sb.toString());
477 
478                     sb.delete(0, sb.length());
479                 }
480             }
481             else {
482                 if (sb.length() > 0) {
483                     sb.append("[*]");
484                 }
485 
486                 sb.append(item);
487             }
488         }
489 
490         return items.toArray(new String[items.size()]);
491     }
492 
493     private static final String[] _BBCODE_TAGS = {
494         "[b]", "[/b]", "[i]", "[/i]", "[u]", "[/u]", "[s]", "[/s]",
495         "[img]", "[/img]",
496         "[left]", "[center]", "[right]", "[indent]",
497         "[/left]", "[/center]", "[/right]", "[/indent]", "[tt]", "[/tt]"
498     };
499 
500     private static final String[] _HTML_TAGS = {
501         "<b>", "</b>", "<i>", "</i>", "<u>", "</u>", "<strike>", "</strike>",
502         "<img alt='' src='", "' />",
503         "<div style='text-align: left'>", "<div style='text-align: center'>",
504         "<div style='text-align: right'>", "<div style='margin-left: 15px'>",
505         "</div>", "</div>", "</div>", "</div>", "<tt>", "</tt>"
506     };
507 
508     private static Log _log = LogFactoryUtil.getLog(BBCodeUtil.class);
509 
510 }