001    /**
002     * Copyright (c) 2000-2013 Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.parsers.bbcode;
016    
017    import com.liferay.portal.kernel.log.Log;
018    import com.liferay.portal.kernel.log.LogFactoryUtil;
019    import com.liferay.portal.kernel.parsers.bbcode.BBCodeTranslator;
020    import com.liferay.portal.kernel.security.pacl.DoPrivileged;
021    import com.liferay.portal.kernel.util.GetterUtil;
022    import com.liferay.portal.kernel.util.HtmlUtil;
023    import com.liferay.portal.kernel.util.IntegerWrapper;
024    import com.liferay.portal.kernel.util.StringBundler;
025    import com.liferay.portal.kernel.util.StringPool;
026    import com.liferay.portal.kernel.util.StringUtil;
027    import com.liferay.portal.kernel.util.Validator;
028    
029    import java.util.Arrays;
030    import java.util.Collection;
031    import java.util.HashMap;
032    import java.util.HashSet;
033    import java.util.List;
034    import java.util.Map;
035    import java.util.Set;
036    import java.util.Stack;
037    import java.util.regex.Matcher;
038    import java.util.regex.Pattern;
039    
040    /**
041     * @author Iliyan Peychev
042     */
043    @DoPrivileged
044    public class HtmlBBCodeTranslatorImpl implements BBCodeTranslator {
045    
046            public HtmlBBCodeTranslatorImpl() {
047                    _listStyles = new HashMap<String, String>();
048    
049                    _listStyles.put("a", "list-style: lower-alpha outside;");
050                    _listStyles.put("A", "list-style: upper-alpha outside;");
051                    _listStyles.put("1", "list-style: decimal outside;");
052                    _listStyles.put("i", "list-style: lower-roman outside;");
053                    _listStyles.put("I", "list-style: upper-roman outside;");
054    
055                    _excludeNewLineTypes = new HashMap<String, Integer>();
056    
057                    _excludeNewLineTypes.put("*", BBCodeParser.TYPE_TAG_START_END);
058                    _excludeNewLineTypes.put("li", BBCodeParser.TYPE_TAG_START_END);
059                    _excludeNewLineTypes.put("table", BBCodeParser.TYPE_TAG_END);
060                    _excludeNewLineTypes.put("td", BBCodeParser.TYPE_TAG_START_END);
061                    _excludeNewLineTypes.put("th", BBCodeParser.TYPE_TAG_START_END);
062                    _excludeNewLineTypes.put("tr", BBCodeParser.TYPE_TAG_START_END);
063    
064                    _bbCodeCharacters = new HashMap<String, String>();
065    
066                    _bbCodeCharacters.put("&", "&amp;");
067                    _bbCodeCharacters.put("<", "&lt;");
068                    _bbCodeCharacters.put(">", "&gt;");
069                    _bbCodeCharacters.put("\"", "&#034;");
070                    _bbCodeCharacters.put("'", "&#039;");
071                    _bbCodeCharacters.put("/", "&#047;");
072                    _bbCodeCharacters.put("`", "&#096;");
073                    _bbCodeCharacters.put("[", "&#91;");
074                    _bbCodeCharacters.put("]", "&#93;");
075                    _bbCodeCharacters.put("(", "&#40;");
076                    _bbCodeCharacters.put(")", "&#41;");
077    
078                    for (int i = 0; i < _EMOTICONS.length; i++) {
079                            String[] emoticon = _EMOTICONS[i];
080    
081                            _emoticonDescriptions[i] = emoticon[2];
082                            _emoticonFiles[i] = emoticon[0];
083                            _emoticonSymbols[i] = emoticon[1];
084    
085                            String image = emoticon[0];
086    
087                            emoticon[0] =
088                                    "<img alt=\"emoticon\" src=\"@theme_images_path@/emoticons/" +
089                                            image + "\" >";
090                    }
091    
092                    _imageAttributes = new HashSet<String>(
093                            Arrays.asList(
094                                    "alt", "class", "dir", "height", "id", "lang", "longdesc",
095                                    "style", "title", "width"));
096            }
097    
098            @Override
099            public String[] getEmoticonDescriptions() {
100                    return _emoticonDescriptions;
101            }
102    
103            @Override
104            public String[] getEmoticonFiles() {
105                    return _emoticonFiles;
106            }
107    
108            @Override
109            public String[][] getEmoticons() {
110                    return _EMOTICONS;
111            }
112    
113            @Override
114            public String[] getEmoticonSymbols() {
115                    return _emoticonSymbols;
116            }
117    
118            @Override
119            public String getHTML(String bbcode) {
120                    try {
121                            bbcode = parse(bbcode);
122                    }
123                    catch (Exception e) {
124                            _log.error("Unable to parse: " + bbcode, e);
125    
126                            bbcode = HtmlUtil.escape(bbcode);
127                    }
128    
129                    return bbcode;
130            }
131    
132            @Override
133            public String parse(String text) {
134                    StringBundler sb = new StringBundler();
135    
136                    List<BBCodeItem> bbCodeItems = _bbCodeParser.parse(text);
137                    Stack<String> tags = new Stack<String>();
138                    IntegerWrapper marker = new IntegerWrapper();
139    
140                    for (; marker.getValue() < bbCodeItems.size(); marker.increment()) {
141                            BBCodeItem bbCodeItem = bbCodeItems.get(marker.getValue());
142    
143                            int type = bbCodeItem.getType();
144    
145                            if (type == BBCodeParser.TYPE_DATA) {
146                                    handleData(sb, bbCodeItems, tags, marker, bbCodeItem);
147                            }
148                            else if (type == BBCodeParser.TYPE_TAG_END) {
149                                    handleTagEnd(sb, tags, bbCodeItem);
150                            }
151                            else if (type == BBCodeParser.TYPE_TAG_START) {
152                                    handleTagStart(sb, bbCodeItems, tags, marker, bbCodeItem);
153                            }
154                    }
155    
156                    return sb.toString();
157            }
158    
159            protected String escapeQuote(String quote) {
160                    StringBuilder sb = new StringBuilder();
161    
162                    int index = 0;
163    
164                    Matcher matcher = _bbCodePattern.matcher(quote);
165    
166                    Collection<String> values = _bbCodeCharacters.values();
167    
168                    while (matcher.find()) {
169                            String match = matcher.group();
170    
171                            int matchStartIndex = matcher.start();
172    
173                            int nextSemicolonIndex = quote.indexOf(
174                                    StringPool.SEMICOLON, matchStartIndex);
175    
176                            sb.append(quote.substring(index, matchStartIndex));
177    
178                            boolean entityFound = false;
179    
180                            if (nextSemicolonIndex >= 0) {
181                                    String value = quote.substring(
182                                            matchStartIndex, nextSemicolonIndex + 1);
183    
184                                    if (values.contains(value)) {
185                                            sb.append(value);
186    
187                                            index = matchStartIndex + value.length();
188    
189                                            entityFound = true;
190                                    }
191                            }
192    
193                            if (!entityFound) {
194                                    String escapedValue = _bbCodeCharacters.get(match);
195    
196                                    sb.append(escapedValue);
197    
198                                    index = matchStartIndex + match.length();
199                            }
200                    }
201    
202                    if (index < quote.length()) {
203                            sb.append(quote.substring(index, quote.length()));
204                    }
205    
206                    return sb.toString();
207            }
208    
209            protected String extractData(
210                    List<BBCodeItem> bbCodeItems, IntegerWrapper marker, String tag,
211                    int type, boolean consume) {
212    
213                    StringBundler sb = new StringBundler();
214    
215                    int index = marker.getValue() + 1;
216    
217                    BBCodeItem bbCodeItem = null;
218    
219                    do {
220                            bbCodeItem = bbCodeItems.get(index++);
221    
222                            if ((bbCodeItem.getType() & type) > 0) {
223                                    sb.append(bbCodeItem.getValue());
224                            }
225                    }
226                    while ((bbCodeItem.getType() != BBCodeParser.TYPE_TAG_END) &&
227                               !tag.equals(bbCodeItem.getValue()));
228    
229                    if (consume) {
230                            marker.setValue(index - 1);
231                    }
232    
233                    return sb.toString();
234            }
235    
236            protected void handleBold(StringBundler sb, Stack<String> tags) {
237                    handleSimpleTag(sb, tags, "strong");
238            }
239    
240            protected void handleCode(
241                    StringBundler sb, List<BBCodeItem> bbCodeItems, IntegerWrapper marker) {
242    
243                    sb.append("<div class=\"lfr-code\">");
244                    sb.append("<table>");
245                    sb.append("<tbody>");
246                    sb.append("<tr>");
247                    sb.append("<td class=\"line-numbers\">");
248    
249                    String code = extractData(
250                            bbCodeItems, marker, "code", BBCodeParser.TYPE_DATA, true);
251    
252                    code = HtmlUtil.escape(code);
253                    code = code.replaceAll(StringPool.TAB, StringPool.FOUR_SPACES);
254    
255                    String[] lines = code.split("\r?\n");
256    
257                    for (int i = 0; i < lines.length; i++) {
258                            String index = String.valueOf(i + 1);
259    
260                            sb.append("<span class=\"number\">");
261                            sb.append(index);
262                            sb.append("</span>");
263                    }
264    
265                    sb.append("</td>");
266                    sb.append("<td class=\"lines\">");
267    
268                    for (int i = 0; i < lines.length; i++) {
269                            String line = lines[i];
270    
271                            line = StringUtil.replace(
272                                    line, StringPool.THREE_SPACES, "&nbsp; &nbsp;");
273                            line = StringUtil.replace(line, StringPool.DOUBLE_SPACE, "&nbsp; ");
274    
275                            if (Validator.isNull(line)) {
276                                    line = "<br />";
277                            }
278    
279                            sb.append("<div class=\"line\">");
280                            sb.append(line);
281                            sb.append("</div>");
282                    }
283    
284                    sb.append("</td>");
285                    sb.append("</tr>");
286                    sb.append("</tbody>");
287                    sb.append("</table>");
288                    sb.append("</div>");
289            }
290    
291            protected void handleColor(
292                    StringBundler sb, Stack<String> tags, BBCodeItem bbCodeItem) {
293    
294                    sb.append("<span style=\"color: ");
295    
296                    String color = bbCodeItem.getAttribute();
297    
298                    if (color == null) {
299                            color = "inherit";
300                    }
301                    else {
302                            Matcher matcher = _colorPattern.matcher(color);
303    
304                            if (!matcher.matches()) {
305                                    color = "inherit";
306                            }
307                    }
308    
309                    sb.append(color);
310    
311                    sb.append("\">");
312    
313                    tags.push("</span>");
314            }
315    
316            protected void handleData(
317                    StringBundler sb, List<BBCodeItem> bbCodeItems, Stack<String> tags,
318                    IntegerWrapper marker, BBCodeItem bbCodeItem) {
319    
320                    String value = HtmlUtil.escape(bbCodeItem.getValue());
321    
322                    value = handleNewLine(bbCodeItems, tags, marker, value);
323    
324                    for (int i = 0; i < _EMOTICONS.length; i++) {
325                            String[] emoticon = _EMOTICONS[i];
326    
327                            value = StringUtil.replace(value, emoticon[1], emoticon[0]);
328                    }
329    
330                    sb.append(value);
331            }
332    
333            protected void handleEmail(
334                    StringBundler sb, List<BBCodeItem> bbCodeItems, Stack<String> tags,
335                    IntegerWrapper marker, BBCodeItem bbCodeItem) {
336    
337                    sb.append("<a href=\"");
338    
339                    String href = bbCodeItem.getAttribute();
340    
341                    if (href == null) {
342                            href = extractData(
343                                    bbCodeItems, marker, "email", BBCodeParser.TYPE_DATA, false);
344                    }
345    
346                    if (!href.startsWith("mailto:")) {
347                            href = "mailto:" + href;
348                    }
349    
350                    sb.append(HtmlUtil.escapeHREF(href));
351    
352                    sb.append("\">");
353    
354                    tags.push("</a>");
355            }
356    
357            protected void handleFontFamily(
358                    StringBundler sb, Stack<String> tags, BBCodeItem bbCodeItem) {
359    
360                    sb.append("<span style=\"font-family: ");
361                    sb.append(HtmlUtil.escapeAttribute(bbCodeItem.getAttribute()));
362                    sb.append("\">");
363    
364                    tags.push("</span>");
365            }
366    
367            protected void handleFontSize(
368                    StringBundler sb, Stack<String> tags, BBCodeItem bbCodeItem) {
369    
370                    sb.append("<span style=\"font-size: ");
371    
372                    int size = GetterUtil.getInteger(bbCodeItem.getAttribute());
373    
374                    if ((size >= 1) && (size <= _fontSizes.length)) {
375                            sb.append(_fontSizes[size - 1]);
376                    }
377                    else {
378                            sb.append(_fontSizes[1]);
379                    }
380    
381                    sb.append("px\">");
382    
383                    tags.push("</span>");
384            }
385    
386            protected void handleImage(
387                    StringBundler sb, List<BBCodeItem> bbCodeItems, IntegerWrapper marker) {
388    
389                    sb.append("<img src=\"");
390    
391                    int pos = marker.getValue();
392    
393                    String src = extractData(
394                            bbCodeItems, marker, "img", BBCodeParser.TYPE_DATA, true);
395    
396                    Matcher matcher = _imagePattern.matcher(src);
397    
398                    if (matcher.matches()) {
399                            sb.append(HtmlUtil.escapeAttribute(src));
400                    }
401    
402                    sb.append("\"");
403    
404                    BBCodeItem bbCodeItem = bbCodeItems.get(pos);
405    
406                    String attributes = bbCodeItem.getAttribute();
407    
408                    if (Validator.isNotNull(attributes)) {
409                            sb.append(StringPool.SPACE);
410    
411                            handleImageAttributes(sb, attributes);
412                    }
413    
414                    sb.append(" />");
415            }
416    
417            protected void handleImageAttributes(StringBundler sb, String attributes) {
418                    Matcher matcher = _attributesPattern.matcher(attributes);
419    
420                    while (matcher.find()) {
421                            String attributeName = matcher.group(1);
422    
423                            if (Validator.isNotNull(attributeName) &&
424                                    _imageAttributes.contains(
425                                            StringUtil.toLowerCase(attributeName))) {
426    
427                                    String attributeValue = matcher.group(2);
428    
429                                    sb.append(StringPool.SPACE);
430                                    sb.append(attributeName);
431                                    sb.append(StringPool.EQUAL);
432                                    sb.append(StringPool.QUOTE);
433                                    sb.append(HtmlUtil.escapeAttribute(attributeValue));
434                                    sb.append(StringPool.QUOTE);
435                            }
436                    }
437            }
438    
439            protected void handleItalic(StringBundler sb, Stack<String> tags) {
440                    handleSimpleTag(sb, tags, "em");
441            }
442    
443            protected void handleList(
444                    StringBundler sb, Stack<String> tags, BBCodeItem bbCodeItem) {
445    
446                    String listStyle = null;
447    
448                    String tag = null;
449    
450                    String listAttribute = bbCodeItem.getAttribute();
451    
452                    if (listAttribute != null) {
453                            listStyle = _listStyles.get(listAttribute);
454    
455                            tag = "ol";
456                    }
457                    else {
458                            listStyle = "list-style: disc outside;";
459    
460                            tag = "ul";
461                    }
462    
463                    sb.append("<");
464                    sb.append(tag);
465    
466                    if (Validator.isNotNull(listStyle)) {
467                            sb.append(" style=\"");
468                            sb.append(listStyle);
469                            sb.append("\"");
470                    }
471    
472                    sb.append(">");
473    
474                    tags.push("</" + tag + ">");
475            }
476    
477            protected void handleListItem(StringBundler sb, Stack<String> tags) {
478                    handleSimpleTag(sb, tags, "li");
479            }
480    
481            protected String handleNewLine(
482                    List<BBCodeItem> bbCodeItems, Stack<String> tags, IntegerWrapper marker,
483                    String data) {
484    
485                    BBCodeItem bbCodeItem = null;
486    
487                    if ((marker.getValue() + 1) < bbCodeItems.size()) {
488                            if (data.matches("\\A\r?\n\\z")) {
489                                    bbCodeItem = bbCodeItems.get(marker.getValue() + 1);
490    
491                                    if (bbCodeItem != null) {
492                                            String value = bbCodeItem.getValue();
493    
494                                            if (_excludeNewLineTypes.containsKey(value)) {
495                                                    int type = bbCodeItem.getType();
496    
497                                                    int excludeNewLineType = _excludeNewLineTypes.get(
498                                                            value);
499    
500                                                    if ((type & excludeNewLineType) > 0) {
501                                                            data = StringPool.BLANK;
502                                                    }
503                                            }
504                                    }
505                            }
506                            else if (data.matches("(?s).*\r?\n\\z")) {
507                                    bbCodeItem = bbCodeItems.get(marker.getValue() + 1);
508    
509                                    if ((bbCodeItem != null) &&
510                                            (bbCodeItem.getType() == BBCodeParser.TYPE_TAG_END)) {
511    
512                                            String value = bbCodeItem.getValue();
513    
514                                            if (value.equals("*")) {
515                                                    data = data.substring(0, data.length() - 1);
516                                            }
517                                    }
518                            }
519                    }
520    
521                    if (data.length() > 0) {
522                            data = data.replaceAll("\r?\n", "<br />");
523                    }
524    
525                    return data;
526            }
527    
528            protected void handleQuote(
529                    StringBundler sb, Stack<String> tags, BBCodeItem bbCodeItem) {
530    
531                    String quote = bbCodeItem.getAttribute();
532    
533                    if ((quote != null) && (quote.length() > 0)) {
534                            sb.append("<div class=\"quote-title\">");
535                            sb.append(escapeQuote(quote));
536                            sb.append(":</div>");
537                    }
538    
539                    sb.append("<div class=\"quote\"><div class=\"quote-content\">");
540    
541                    tags.push("</div></div>");
542            }
543    
544            protected void handleSimpleTag(
545                    StringBundler sb, Stack<String> tags, BBCodeItem bbCodeItem) {
546    
547                    handleSimpleTag(sb, tags, bbCodeItem.getValue());
548            }
549    
550            protected void handleSimpleTag(
551                    StringBundler sb, Stack<String> tags, String tag) {
552    
553                    sb.append("<");
554                    sb.append(tag);
555                    sb.append(">");
556    
557                    tags.push("</" + tag + ">");
558            }
559    
560            protected void handleStrikeThrough(StringBundler sb, Stack<String> tags) {
561                    handleSimpleTag(sb, tags, "strike");
562            }
563    
564            protected void handleTable(StringBundler sb, Stack<String> tags) {
565                    handleSimpleTag(sb, tags, "table");
566            }
567    
568            protected void handleTableCell(StringBundler sb, Stack<String> tags) {
569                    handleSimpleTag(sb, tags, "td");
570            }
571    
572            protected void handleTableHeader(StringBundler sb, Stack<String> tags) {
573                    handleSimpleTag(sb, tags, "th");
574            }
575    
576            protected void handleTableRow(StringBundler sb, Stack<String> tags) {
577                    handleSimpleTag(sb, tags, "tr");
578            }
579    
580            protected void handleTagEnd(
581                    StringBundler sb, Stack<String> tags, BBCodeItem bbCodeItem) {
582    
583                    String tag = bbCodeItem.getValue();
584    
585                    if (isValidTag(tag)) {
586                            sb.append(tags.pop());
587                    }
588            }
589    
590            protected void handleTagStart(
591                    StringBundler sb, List<BBCodeItem> bbCodeItems, Stack<String> tags,
592                    IntegerWrapper marker, BBCodeItem bbCodeItem) {
593    
594                    String tag = bbCodeItem.getValue();
595    
596                    if (!isValidTag(tag)) {
597                            return;
598                    }
599    
600                    if (tag.equals("b")) {
601                            handleBold(sb, tags);
602                    }
603                    else if (tag.equals("center") || tag.equals("justify") ||
604                                     tag.equals("left") || tag.equals("right")) {
605    
606                            handleTextAlign(sb, tags, bbCodeItem);
607                    }
608                    else if (tag.equals("code")) {
609                            handleCode(sb, bbCodeItems, marker);
610                    }
611                    else if (tag.equals("color") || tag.equals("colour")) {
612                            handleColor(sb, tags, bbCodeItem);
613                    }
614                    else if (tag.equals("email")) {
615                            handleEmail(sb, bbCodeItems, tags, marker, bbCodeItem);
616                    }
617                    else if (tag.equals("font")) {
618                            handleFontFamily(sb, tags, bbCodeItem);
619                    }
620                    else if (tag.equals("i")) {
621                            handleItalic(sb, tags);
622                    }
623                    else if (tag.equals("img")) {
624                            handleImage(sb, bbCodeItems, marker);
625                    }
626                    else if (tag.equals("li") || tag.equals("*")) {
627                            handleListItem(sb, tags);
628                    }
629                    else if (tag.equals("list")) {
630                            handleList(sb, tags, bbCodeItem);
631                    }
632                    else if (tag.equals("q") || tag.equals("quote")) {
633                            handleQuote(sb, tags, bbCodeItem);
634                    }
635                    else if (tag.equals("s")) {
636                            handleStrikeThrough(sb, tags);
637                    }
638                    else if (tag.equals("size")) {
639                            handleFontSize(sb, tags, bbCodeItem);
640                    }
641                    else if (tag.equals("table")) {
642                            handleTable(sb, tags);
643                    }
644                    else if (tag.equals("td")) {
645                            handleTableCell(sb, tags);
646                    }
647                    else if (tag.equals("th")) {
648                            handleTableHeader(sb, tags);
649                    }
650                    else if (tag.equals("tr")) {
651                            handleTableRow(sb, tags);
652                    }
653                    else if (tag.equals("url")) {
654                            handleURL(sb, bbCodeItems, tags, marker, bbCodeItem);
655                    }
656                    else {
657                            handleSimpleTag(sb, tags, bbCodeItem);
658                    }
659            }
660    
661            protected void handleTextAlign(
662                    StringBundler sb, Stack<String> tags, BBCodeItem bbCodeItem) {
663    
664                    sb.append("<p style=\"text-align: ");
665                    sb.append(bbCodeItem.getValue());
666                    sb.append("\">");
667    
668                    tags.push("</p>");
669            }
670    
671            protected void handleURL(
672                    StringBundler sb, List<BBCodeItem> bbCodeItems, Stack<String> tags,
673                    IntegerWrapper marker, BBCodeItem bbCodeItem) {
674    
675                    sb.append("<a href=\"");
676    
677                    String href = bbCodeItem.getAttribute();
678    
679                    if (href == null) {
680                            href = extractData(
681                                    bbCodeItems, marker, "url", BBCodeParser.TYPE_DATA, false);
682                    }
683    
684                    Matcher matcher = _urlPattern.matcher(href);
685    
686                    if (matcher.matches()) {
687                            sb.append(HtmlUtil.escapeHREF(href));
688                    }
689    
690                    sb.append("\">");
691    
692                    tags.push("</a>");
693            }
694    
695            protected boolean isValidTag(String tag) {
696                    if ((tag != null) && (tag.length() > 0)) {
697                            Matcher matcher = _tagPattern.matcher(tag);
698    
699                            return matcher.matches();
700                    }
701    
702                    return false;
703            }
704    
705            private static final String[][] _EMOTICONS = {
706                    {"happy.gif", ":)", "happy"},
707                    {"smile.gif", ":D", "smile"},
708                    {"cool.gif", "B)", "cool"},
709                    {"sad.gif", ":(", "sad"},
710                    {"tongue.gif", ":P", "tongue"},
711                    {"laugh.gif", ":lol:", "laugh"},
712                    {"kiss.gif", ":#", "kiss"},
713                    {"blush.gif", ":*)", "blush"},
714                    {"bashful.gif", ":bashful:", "bashful"},
715                    {"smug.gif", ":smug:", "smug"},
716                    {"blink.gif", ":blink:", "blink"},
717                    {"huh.gif", ":huh:", "huh"},
718                    {"mellow.gif", ":mellow:", "mellow"},
719                    {"unsure.gif", ":unsure:", "unsure"},
720                    {"mad.gif", ":mad:", "mad"},
721                    {"oh_my.gif", ":O", "oh-my-goodness"},
722                    {"roll_eyes.gif", ":rolleyes:", "roll-eyes"},
723                    {"angry.gif", ":angry:", "angry"},
724                    {"suspicious.gif", "8o", "suspicious"},
725                    {"big_grin.gif", ":grin:", "grin"},
726                    {"in_love.gif", ":love:", "in-love"},
727                    {"bored.gif", ":bored:", "bored"},
728                    {"closed_eyes.gif", "-_-", "closed-eyes"},
729                    {"cold.gif", ":cold:", "cold"},
730                    {"sleep.gif", ":sleep:", "sleep"},
731                    {"glare.gif", ":glare:", "glare"},
732                    {"darth_vader.gif", ":vader:", "darth-vader"},
733                    {"dry.gif", ":dry:", "dry"},
734                    {"exclamation.gif", ":what:", "what"},
735                    {"girl.gif", ":girl:", "girl"},
736                    {"karate_kid.gif", ":kid:", "karate-kid"},
737                    {"ninja.gif", ":ph34r:", "ninja"},
738                    {"pac_man.gif", ":V", "pac-man"},
739                    {"wacko.gif", ":wacko:", "wacko"},
740                    {"wink.gif", ":wink:", "wink"},
741                    {"wub.gif", ":wub:", "wub"}
742            };
743    
744            private static Log _log = LogFactoryUtil.getLog(
745                    HtmlBBCodeTranslatorImpl.class);
746    
747            private Pattern _attributesPattern = Pattern.compile(
748                    "\\s*([^=]+)\\s*=\\s*\"([^\"]+)\"\\s*");
749            private Map<String, String> _bbCodeCharacters;
750            private BBCodeParser _bbCodeParser = new BBCodeParser();
751            private Pattern _bbCodePattern = Pattern.compile("[]&<>'\"`\\[()]");
752            private Pattern _colorPattern = Pattern.compile(
753                    "^(:?aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|purple" +
754                            "|red|silver|teal|white|yellow|#(?:[0-9a-f]{3})?[0-9a-f]{3})$",
755                    Pattern.CASE_INSENSITIVE);
756            private String[] _emoticonDescriptions = new String[_EMOTICONS.length];
757            private String[] _emoticonFiles = new String[_EMOTICONS.length];
758            private String[] _emoticonSymbols = new String[_EMOTICONS.length];
759            private Map<String, Integer> _excludeNewLineTypes;
760            private int[] _fontSizes = {10, 12, 16, 18, 24, 32, 48};
761            private Set<String> _imageAttributes;
762            private Pattern _imagePattern = Pattern.compile(
763                    "^(?:https?://|/)[-;/?:@&=+$,_.!~*'()%0-9a-z]{1,512}$",
764                    Pattern.CASE_INSENSITIVE);
765            private Map<String, String> _listStyles;
766            private Pattern _tagPattern = Pattern.compile(
767                    "^/?(?:b|center|code|colou?r|email|i|img|justify|left|pre|q|quote|" +
768                            "right|\\*|s|size|table|tr|th|td|li|list|font|u|url)$",
769                    Pattern.CASE_INSENSITIVE);
770            private Pattern _urlPattern = Pattern.compile(
771                    "^[-;/?:@&=+$,_.!~*'()%0-9a-z#]{1,512}$", Pattern.CASE_INSENSITIVE);
772    
773    }