1   /**
2    * Copyright (c) 2000-2008 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.util.GetterUtil;
26  import com.liferay.portal.kernel.util.HtmlUtil;
27  import com.liferay.portal.kernel.util.StringPool;
28  import com.liferay.portal.kernel.util.StringUtil;
29  
30  import java.util.ArrayList;
31  import java.util.HashMap;
32  import java.util.List;
33  import java.util.Map;
34  
35  import org.apache.commons.logging.Log;
36  import org.apache.commons.logging.LogFactory;
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(String bbcode) {
120         String html = HtmlUtil.escape(bbcode);
121 
122         html = StringUtil.replace(html, _BBCODE_TAGS, _HTML_TAGS);
123 
124         for (int i = 0; i < emoticons.length; i++) {
125             String[] emoticon = emoticons[i];
126 
127             html = StringUtil.replace(html, emoticon[1], emoticon[0]);
128         }
129 
130         BBCodeTag tag = null;
131 
132         StringBuilder sb = null;
133 
134         while ((tag = getFirstTag(html, "code")) != null) {
135             String preTag = html.substring(0, tag.getStartPos());
136             String postTag = html.substring(tag.getEndPos());
137 
138             String code = tag.getElement().replaceAll(
139                 "\t", StringPool.FOUR_SPACES);
140             String[] lines = code.split("\\n");
141             int digits = String.valueOf(lines.length + 1).length();
142 
143             sb = new StringBuilder(preTag);
144 
145             sb.append("<div class='code'>");
146 
147             for (int i = 0; i < lines.length; i++) {
148                 String index = String.valueOf(i + 1);
149                 int ld = index.length();
150 
151                 sb.append("<span class='code-lines'>");
152 
153                 for (int j = 0; j < digits - ld; j++) {
154                     sb.append("&nbsp;");
155                 }
156 
157                 lines[i] = StringUtil.replace(lines[i], "   ",
158                     StringPool.NBSP + StringPool.SPACE + StringPool.NBSP);
159                 lines[i] = StringUtil.replace(lines[i], "  ",
160                     StringPool.NBSP + StringPool.SPACE);
161 
162                 sb.append(index + "</span>");
163                 sb.append(lines[i]);
164 
165                 if (index.length() < lines.length) {
166                     sb.append("<br />");
167                 }
168             }
169 
170             sb.append("</div>");
171             sb.append(postTag);
172 
173             html = sb.toString();
174         }
175 
176         while ((tag = getFirstTag(html, "color")) != null) {
177             String preTag = html.substring(0, tag.getStartPos());
178             String postTag = html.substring(tag.getEndPos());
179 
180             sb = new StringBuilder(preTag);
181 
182             if (tag.hasParameter()) {
183                 sb.append("<span style='color: ");
184                 sb.append(tag.getParameter() + ";'>");
185                 sb.append(tag.getElement() + "</span>");
186             }
187             else {
188                 sb.append(tag.getElement());
189             }
190 
191             sb.append(postTag);
192 
193             html = sb.toString();
194         }
195 
196         while ((tag = getFirstTag(html, "email")) != null) {
197             String preTag = html.substring(0, tag.getStartPos());
198             String postTag = html.substring(tag.getEndPos());
199 
200             String mailto = GetterUtil.getString(
201                 tag.getParameter(), tag.getElement().trim());
202 
203             sb = new StringBuilder(preTag);
204 
205             sb.append("<a href='mailto: " + mailto + "'>");
206             sb.append(tag.getElement() + "</a>");
207             sb.append(postTag);
208 
209             html = sb.toString();
210         }
211 
212         while ((tag = getFirstTag(html, "font")) != null) {
213             String preTag = html.substring(0, tag.getStartPos());
214             String postTag = html.substring(tag.getEndPos());
215 
216             sb = new StringBuilder(preTag);
217 
218             if (tag.hasParameter()) {
219                 sb.append("<span style='font-family: ");
220                 sb.append(tag.getParameter() + "';>");
221                 sb.append(tag.getElement() + "</span>");
222             }
223             else {
224                 sb.append(tag.getElement());
225             }
226 
227             sb.append(postTag);
228 
229             html = sb.toString();
230         }
231 
232         while ((tag = getFirstTag(html, "img")) != null) {
233             String preTag = html.substring(0, tag.getStartPos());
234             String postTag = html.substring(tag.getEndPos());
235 
236             sb = new StringBuilder(preTag);
237 
238             sb.append("<img alt='' src='" + tag.getElement().trim() + "' />");
239             sb.append(postTag);
240 
241             html = sb.toString();
242         }
243 
244         while ((tag = getFirstTag(html, "list")) != null) {
245             String preTag = html.substring(0, tag.getStartPos());
246             String postTag = html.substring(tag.getEndPos());
247 
248             String[] items = _getListItems(tag.getElement());
249 
250             sb = new StringBuilder(preTag);
251 
252             if (tag.hasParameter() &&
253                 listStyles.containsKey(tag.getParameter())) {
254 
255                 sb.append(listStyles.get(tag.getParameter()));
256 
257                 for (int i = 0; i < items.length; i++) {
258                     if (items[i].trim().length() > 0) {
259                         sb.append("<li>" + items[i].trim() + "</li>");
260                     }
261                 }
262 
263                 sb.append("</ol>");
264             }
265             else {
266                 sb.append("<ul style='list-style-type: disc';>");
267 
268                 for (int i = 0; i < items.length; i++) {
269                     if (items[i].trim().length() > 0) {
270                         sb.append("<li>" + items[i].trim() + "</li>");
271                     }
272                 }
273 
274                 sb.append("</ul>");
275             }
276 
277             sb.append(postTag);
278 
279             html = sb.toString();
280         }
281 
282         while ((tag = getFirstTag(html, "quote")) != null) {
283             String preTag = html.substring(0, tag.getStartPos());
284             String postTag = html.substring(tag.getEndPos());
285 
286             sb = new StringBuilder(preTag);
287 
288             if (tag.hasParameter()) {
289                 sb.append("<div class='quote-title'>");
290                 sb.append(tag.getParameter() + ":</div>");
291             }
292 
293             sb.append("<div class='quote'>");
294             sb.append("<div class='quote-content'>");
295             sb.append(tag.getElement());
296             sb.append("</div></div>");
297             sb.append(postTag);
298 
299             html = sb.toString();
300         }
301 
302         while ((tag = getFirstTag(html, "size")) != null) {
303             String preTag = html.substring(0, tag.getStartPos());
304             String postTag = html.substring(tag.getEndPos());
305 
306             sb = new StringBuilder(preTag);
307 
308             if (tag.hasParameter()) {
309                 Integer size = new Integer(
310                     GetterUtil.getInteger(tag.getParameter()));
311 
312                 if (size.intValue() > 7) {
313                     size = new Integer(7);
314                 }
315 
316                 if (fontSizes.containsKey(size)) {
317                     sb.append(fontSizes.get(size));
318                     sb.append(tag.getElement() + "</span>");
319                 }
320                 else {
321                     sb.append(tag.getElement());
322                 }
323             }
324             else {
325                 sb.append(tag.getElement());
326             }
327 
328             sb.append(postTag);
329 
330             html = sb.toString();
331         }
332 
333         while ((tag = getFirstTag(html, "url")) != null) {
334             String preTag = html.substring(0, tag.getStartPos());
335             String postTag = html.substring(tag.getEndPos());
336 
337             String url = GetterUtil.getString(
338                 tag.getParameter(), tag.getElement().trim());
339 
340             sb = new StringBuilder(preTag);
341 
342             sb.append("<a href='" + url + "'>");
343             sb.append(tag.getElement() + "</a>");
344             sb.append(postTag);
345 
346             html = sb.toString();
347         }
348 
349         html = StringUtil.replace(html, "\n", "<br />");
350 
351         return html;
352     }
353 
354     public static BBCodeTag getFirstTag(String bbcode, String name) {
355         BBCodeTag tag = new BBCodeTag();
356 
357         String begTag = "[" + name;
358         String endTag = "[/" + name + "]";
359 
360         String preTag = StringUtil.extractFirst(bbcode, begTag);
361 
362         if (preTag == null) {
363             return null;
364         }
365 
366         if (preTag.length() != bbcode.length()) {
367             tag.setStartPos(preTag.length());
368 
369             String remainder = bbcode.substring(
370                 preTag.length() + begTag.length());
371 
372             int cb = remainder.indexOf("]");
373             int end = _getEndTagPos(remainder, begTag, endTag);
374 
375             if (cb > 0 && remainder.startsWith("=")) {
376                 tag.setParameter(remainder.substring(1, cb));
377                 tag.setElement(remainder.substring(cb + 1, end));
378             }
379             else if (cb == 0) {
380                 try {
381                     tag.setElement(remainder.substring(1, end));
382                 }
383                 catch (StringIndexOutOfBoundsException sioobe) {
384                     _log.error(bbcode);
385 
386                     throw sioobe;
387                 }
388             }
389         }
390 
391         if (tag.hasElement()) {
392             int length =
393                 begTag.length() + 1 + tag.getElement().length() +
394                     endTag.length();
395 
396             if (tag.hasParameter()) {
397                 length += 1 + tag.getParameter().length();
398             }
399 
400             tag.setEndPos(tag.getStartPos() + length);
401 
402             return tag;
403         }
404 
405         return null;
406     }
407 
408     private static int _getEndTagPos(
409         String remainder, String begTag, String endTag) {
410 
411         int nextBegTagPos = remainder.indexOf(begTag);
412         int nextEndTagPos = remainder.indexOf(endTag);
413 
414         while ((nextBegTagPos < nextEndTagPos) && (nextBegTagPos >= 0)) {
415             nextBegTagPos = remainder.indexOf(
416                 begTag, nextBegTagPos + begTag.length());
417             nextEndTagPos = remainder.indexOf(
418                 endTag, nextEndTagPos + endTag.length());
419         }
420 
421         return nextEndTagPos;
422     }
423 
424     private static String[] _getListItems(String tagElement) {
425         List<String> items = new ArrayList<String>();
426 
427         StringBuilder sb = new StringBuilder();
428 
429         int nestLevel = 0;
430 
431         for (String item : StringUtil.split(tagElement, "[*]")) {
432             item = item.trim();
433 
434             if (item.length() == 0) {
435                 continue;
436             }
437 
438             int begTagCount = StringUtil.count(item, "[list");
439 
440             if (begTagCount > 0) {
441                 nestLevel += begTagCount;
442             }
443 
444             int endTagCount = StringUtil.count(item, "[/list]");
445 
446             if (endTagCount > 0) {
447                 nestLevel -= endTagCount;
448             }
449 
450             if (nestLevel == 0) {
451                 if ((begTagCount == 0) && (endTagCount == 0)) {
452                     items.add(item);
453                 }
454                 else if (endTagCount > 0) {
455                     if (sb.length() > 0) {
456                         sb.append("[*]");
457                     }
458 
459                     sb.append(item);
460 
461                     items.add(sb.toString());
462 
463                     sb.delete(0, sb.length());
464                 }
465             }
466             else {
467                 if (sb.length() > 0) {
468                     sb.append("[*]");
469                 }
470 
471                 sb.append(item);
472             }
473         }
474 
475         return items.toArray(new String[items.size()]);
476     }
477 
478     private static final String[] _BBCODE_TAGS = {
479         "[b]", "[/b]", "[i]", "[/i]", "[u]", "[/u]", "[s]", "[/s]",
480         "[img]", "[/img]",
481         "[left]", "[center]", "[right]", "[indent]",
482         "[/left]", "[/center]", "[/right]", "[/indent]", "[tt]", "[/tt]"
483     };
484 
485     private static final String[] _HTML_TAGS = {
486         "<b>", "</b>", "<i>", "</i>", "<u>", "</u>", "<strike>", "</strike>",
487         "<img alt='' src='", "' />",
488         "<div style='text-align: left'>", "<div style='text-align: center'>",
489         "<div style='text-align: right'>", "<div style='margin-left: 15px'>",
490         "</div>", "</div>", "</div>", "</div>", "<tt>", "</tt>"
491     };
492 
493     private static Log _log = LogFactory.getLog(BBCodeUtil.class);
494 
495 }