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