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