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