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  public class BBCodeUtil {
44  
45      static Map<Integer, String> fontSizes = new HashMap<Integer, String>();
46  
47      static Map<String, String> listStyles = new HashMap<String, String>();
48  
49      static String[][] emoticons = {
50          {"angry.gif", ":angry:"},
51          {"bashful.gif", ":bashful:"},
52          {"big_grin.gif", ":grin:"},
53          {"blink.gif", ":blink:"},
54          {"blush.gif", ":*)"},
55          {"bored.gif", ":bored:"},
56          {"closed_eyes.gif", "-_-"},
57          {"cold.gif", ":cold:"},
58          {"cool.gif", "B)"},
59          {"darth_vader.gif", ":vader:"},
60          {"dry.gif", "<_<"},
61          {"exclamation.gif", ":what:"},
62          {"girl.gif", ":girl:"},
63          {"glare.gif", ">_>"},
64          {"happy.gif", ":)"},
65          {"huh.gif", ":huh:"},
66          {"in_love.gif", "<3"},
67          {"karate_kid.gif", ":kid:"},
68          {"kiss.gif", ":#"},
69          {"laugh.gif", ":lol:"},
70          {"mad.gif", ":mad:"},
71          {"mellow.gif", ":mellow:"},
72          {"ninja.gif", ":ph34r:"},
73          {"oh_my.gif", ":O"},
74          {"pac_man.gif", ":V"},
75          {"roll_eyes.gif", ":rolleyes:"},
76          {"sad.gif", ":("},
77          {"sleep.gif", ":sleep:"},
78          {"smile.gif", ":D"},
79          {"smug.gif", ":smug:"},
80          {"suspicious.gif", "8o"},
81          {"tongue.gif", ":P"},
82          {"unsure.gif", ":unsure:"},
83          {"wacko.gif", ":wacko:"},
84          {"wink.gif", ":wink:"},
85          {"wub.gif", ":wub:"}
86      };
87  
88      static {
89          fontSizes.put(new Integer(1), "<span style='font-size: 0.7em';>");
90          fontSizes.put(new Integer(2), "<span style='font-size: 0.8em';>");
91          fontSizes.put(new Integer(3), "<span style='font-size: 0.9em';>");
92          fontSizes.put(new Integer(4), "<span style='font-size: 1.0em';>");
93          fontSizes.put(new Integer(5), "<span style='font-size: 1.1em';>");
94          fontSizes.put(new Integer(6), "<span style='font-size: 1.3em';>");
95          fontSizes.put(new Integer(7), "<span style='font-size: 1.5em';>");
96  
97          listStyles.put("1", "<ol style='list-style-type: decimal';>");
98          listStyles.put("i", "<ol style='list-style-type: lower-roman';>");
99          listStyles.put("I", "<ol style='list-style-type: upper-roman';>");
100         listStyles.put("a", "<ol style='list-style-type: lower-alpha';>");
101         listStyles.put("A", "<ol style='list-style-type: upper-alpha';>");
102 
103         for (int i = 0; i < emoticons.length; i++) {
104             String[] emoticon = emoticons[i];
105 
106             String image = emoticon[0];
107             String code = emoticon[1];
108 
109             emoticon[0] =
110                 "<img alt='emoticon' src='@theme_images_path@/emoticons/" +
111                     image + "' />";
112             emoticon[1] = HtmlUtil.escape(code);
113         }
114     }
115 
116     public static final String[][] EMOTICONS = emoticons;
117 
118     public static String getHTML(MBMessage message) {
119         String body = message.getBody();
120 
121         try {
122             body = getHTML(body);
123         }
124         catch (Exception e) {
125             _log.error(
126                 "Could not parse message " + message.getMessageId() + " " +
127                     e.getMessage());
128         }
129 
130         return body;
131     }
132 
133     public static String getHTML(String bbcode) {
134         String html = HtmlUtil.escape(bbcode);
135 
136         html = StringUtil.replace(html, _BBCODE_TAGS, _HTML_TAGS);
137 
138         for (int i = 0; i < emoticons.length; i++) {
139             String[] emoticon = emoticons[i];
140 
141             html = StringUtil.replace(html, emoticon[1], emoticon[0]);
142         }
143 
144         BBCodeTag tag = null;
145 
146         StringBuilder sb = null;
147 
148         while ((tag = getFirstTag(html, "code")) != null) {
149             String preTag = html.substring(0, tag.getStartPos());
150             String postTag = html.substring(tag.getEndPos());
151 
152             String code = tag.getElement().replaceAll(
153                 "\t", StringPool.FOUR_SPACES);
154             String[] lines = code.split("\\n");
155             int digits = String.valueOf(lines.length + 1).length();
156 
157             sb = new StringBuilder(preTag);
158 
159             sb.append("<div class='code'>");
160 
161             for (int i = 0; i < lines.length; i++) {
162                 String index = String.valueOf(i + 1);
163                 int ld = index.length();
164 
165                 sb.append("<span class='code-lines'>");
166 
167                 for (int j = 0; j < digits - ld; j++) {
168                     sb.append("&nbsp;");
169                 }
170 
171                 lines[i] = StringUtil.replace(lines[i], "   ",
172                     StringPool.NBSP + StringPool.SPACE + StringPool.NBSP);
173                 lines[i] = StringUtil.replace(lines[i], "  ",
174                     StringPool.NBSP + StringPool.SPACE);
175 
176                 sb.append(index + "</span>");
177                 sb.append(lines[i]);
178 
179                 if (index.length() < lines.length) {
180                     sb.append("<br />");
181                 }
182             }
183 
184             sb.append("</div>");
185             sb.append(postTag);
186 
187             html = sb.toString();
188         }
189 
190         while ((tag = getFirstTag(html, "color")) != null) {
191             String preTag = html.substring(0, tag.getStartPos());
192             String postTag = html.substring(tag.getEndPos());
193 
194             sb = new StringBuilder(preTag);
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             sb = new StringBuilder(preTag);
218 
219             sb.append("<a href='mailto: " + mailto + "'>");
220             sb.append(tag.getElement() + "</a>");
221             sb.append(postTag);
222 
223             html = sb.toString();
224         }
225 
226         while ((tag = getFirstTag(html, "font")) != null) {
227             String preTag = html.substring(0, tag.getStartPos());
228             String postTag = html.substring(tag.getEndPos());
229 
230             sb = new StringBuilder(preTag);
231 
232             if (tag.hasParameter()) {
233                 sb.append("<span style='font-family: ");
234                 sb.append(tag.getParameter() + "';>");
235                 sb.append(tag.getElement() + "</span>");
236             }
237             else {
238                 sb.append(tag.getElement());
239             }
240 
241             sb.append(postTag);
242 
243             html = sb.toString();
244         }
245 
246         while ((tag = getFirstTag(html, "img")) != null) {
247             String preTag = html.substring(0, tag.getStartPos());
248             String postTag = html.substring(tag.getEndPos());
249 
250             sb = new StringBuilder(preTag);
251 
252             sb.append("<img alt='' src='" + tag.getElement().trim() + "' />");
253             sb.append(postTag);
254 
255             html = sb.toString();
256         }
257 
258         while ((tag = getFirstTag(html, "list")) != null) {
259             String preTag = html.substring(0, tag.getStartPos());
260             String postTag = html.substring(tag.getEndPos());
261 
262             String[] items = _getListItems(tag.getElement());
263 
264             sb = new StringBuilder(preTag);
265 
266             if (tag.hasParameter() &&
267                 listStyles.containsKey(tag.getParameter())) {
268 
269                 sb.append(listStyles.get(tag.getParameter()));
270 
271                 for (int i = 0; i < items.length; i++) {
272                     if (items[i].trim().length() > 0) {
273                         sb.append("<li>" + items[i].trim() + "</li>");
274                     }
275                 }
276 
277                 sb.append("</ol>");
278             }
279             else {
280                 sb.append("<ul style='list-style-type: disc';>");
281 
282                 for (int i = 0; i < items.length; i++) {
283                     if (items[i].trim().length() > 0) {
284                         sb.append("<li>" + items[i].trim() + "</li>");
285                     }
286                 }
287 
288                 sb.append("</ul>");
289             }
290 
291             sb.append(postTag);
292 
293             html = sb.toString();
294         }
295 
296         while ((tag = getFirstTag(html, "quote")) != null) {
297             String preTag = html.substring(0, tag.getStartPos());
298             String postTag = html.substring(tag.getEndPos());
299 
300             sb = new StringBuilder(preTag);
301 
302             if (tag.hasParameter()) {
303                 sb.append("<div class='quote-title'>");
304                 sb.append(tag.getParameter() + ":</div>");
305             }
306 
307             sb.append("<div class='quote'>");
308             sb.append("<div class='quote-content'>");
309             sb.append(tag.getElement());
310             sb.append("</div></div>");
311             sb.append(postTag);
312 
313             html = sb.toString();
314         }
315 
316         while ((tag = getFirstTag(html, "size")) != null) {
317             String preTag = html.substring(0, tag.getStartPos());
318             String postTag = html.substring(tag.getEndPos());
319 
320             sb = new StringBuilder(preTag);
321 
322             if (tag.hasParameter()) {
323                 Integer size = new Integer(
324                     GetterUtil.getInteger(tag.getParameter()));
325 
326                 if (size.intValue() > 7) {
327                     size = new Integer(7);
328                 }
329 
330                 if (fontSizes.containsKey(size)) {
331                     sb.append(fontSizes.get(size));
332                     sb.append(tag.getElement() + "</span>");
333                 }
334                 else {
335                     sb.append(tag.getElement());
336                 }
337             }
338             else {
339                 sb.append(tag.getElement());
340             }
341 
342             sb.append(postTag);
343 
344             html = sb.toString();
345         }
346 
347         while ((tag = getFirstTag(html, "url")) != null) {
348             String preTag = html.substring(0, tag.getStartPos());
349             String postTag = html.substring(tag.getEndPos());
350 
351             String url = GetterUtil.getString(
352                 tag.getParameter(), tag.getElement().trim());
353 
354             sb = new StringBuilder(preTag);
355 
356             sb.append("<a href='" + url + "'>");
357             sb.append(tag.getElement() + "</a>");
358             sb.append(postTag);
359 
360             html = sb.toString();
361         }
362 
363         html = StringUtil.replace(html, "\n", "<br />");
364 
365         return html;
366     }
367 
368     public static BBCodeTag getFirstTag(String bbcode, String name) {
369         BBCodeTag tag = new BBCodeTag();
370 
371         String begTag = "[" + name;
372         String endTag = "[/" + name + "]";
373 
374         String preTag = StringUtil.extractFirst(bbcode, begTag);
375 
376         if (preTag == null) {
377             return null;
378         }
379 
380         if (preTag.length() != bbcode.length()) {
381             tag.setStartPos(preTag.length());
382 
383             String remainder = bbcode.substring(
384                 preTag.length() + begTag.length());
385 
386             int cb = remainder.indexOf("]");
387             int end = _getEndTagPos(remainder, begTag, endTag);
388 
389             if (cb > 0 && remainder.startsWith("=")) {
390                 tag.setParameter(remainder.substring(1, cb));
391                 tag.setElement(remainder.substring(cb + 1, end));
392             }
393             else if (cb == 0) {
394                 try {
395                     tag.setElement(remainder.substring(1, end));
396                 }
397                 catch (StringIndexOutOfBoundsException sioobe) {
398                     _log.error(bbcode);
399 
400                     throw sioobe;
401                 }
402             }
403         }
404 
405         if (tag.hasElement()) {
406             int length =
407                 begTag.length() + 1 + tag.getElement().length() +
408                     endTag.length();
409 
410             if (tag.hasParameter()) {
411                 length += 1 + tag.getParameter().length();
412             }
413 
414             tag.setEndPos(tag.getStartPos() + length);
415 
416             return tag;
417         }
418 
419         return null;
420     }
421 
422     private static int _getEndTagPos(
423         String remainder, String begTag, String endTag) {
424 
425         int nextBegTagPos = remainder.indexOf(begTag);
426         int nextEndTagPos = remainder.indexOf(endTag);
427 
428         while ((nextBegTagPos < nextEndTagPos) && (nextBegTagPos >= 0)) {
429             nextBegTagPos = remainder.indexOf(
430                 begTag, nextBegTagPos + begTag.length());
431             nextEndTagPos = remainder.indexOf(
432                 endTag, nextEndTagPos + endTag.length());
433         }
434 
435         return nextEndTagPos;
436     }
437 
438     private static String[] _getListItems(String tagElement) {
439         List<String> items = new ArrayList<String>();
440 
441         StringBuilder sb = new StringBuilder();
442 
443         int nestLevel = 0;
444 
445         for (String item : StringUtil.split(tagElement, "[*]")) {
446             item = item.trim();
447 
448             if (item.length() == 0) {
449                 continue;
450             }
451 
452             int begTagCount = StringUtil.count(item, "[list");
453 
454             if (begTagCount > 0) {
455                 nestLevel += begTagCount;
456             }
457 
458             int endTagCount = StringUtil.count(item, "[/list]");
459 
460             if (endTagCount > 0) {
461                 nestLevel -= endTagCount;
462             }
463 
464             if (nestLevel == 0) {
465                 if ((begTagCount == 0) && (endTagCount == 0)) {
466                     items.add(item);
467                 }
468                 else if (endTagCount > 0) {
469                     if (sb.length() > 0) {
470                         sb.append("[*]");
471                     }
472 
473                     sb.append(item);
474 
475                     items.add(sb.toString());
476 
477                     sb.delete(0, sb.length());
478                 }
479             }
480             else {
481                 if (sb.length() > 0) {
482                     sb.append("[*]");
483                 }
484 
485                 sb.append(item);
486             }
487         }
488 
489         return items.toArray(new String[items.size()]);
490     }
491 
492     private static final String[] _BBCODE_TAGS = {
493         "[b]", "[/b]", "[i]", "[/i]", "[u]", "[/u]", "[s]", "[/s]",
494         "[img]", "[/img]",
495         "[left]", "[center]", "[right]", "[indent]",
496         "[/left]", "[/center]", "[/right]", "[/indent]", "[tt]", "[/tt]"
497     };
498 
499     private static final String[] _HTML_TAGS = {
500         "<b>", "</b>", "<i>", "</i>", "<u>", "</u>", "<strike>", "</strike>",
501         "<img alt='' src='", "' />",
502         "<div style='text-align: left'>", "<div style='text-align: center'>",
503         "<div style='text-align: right'>", "<div style='margin-left: 15px'>",
504         "</div>", "</div>", "</div>", "</div>", "<tt>", "</tt>"
505     };
506 
507     private static Log _log = LogFactoryUtil.getLog(BBCodeUtil.class);
508 
509 }