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