/** * * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved. * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * Copyright (c) 2009-2014 Câmara dos Deputados. Todos os direitos reservados. * * e-Democracia é um software livre; você pode redistribuí-lo e/ou modificá-lo dentro * dos termos da Licença Pública Geral Menor GNU como publicada pela Fundação do * Software Livre (FSF); na versão 2.1 da Licença, ou (na sua opinião) qualquer versão. * * Este programa é distribuído na esperança de que possa ser útil, mas SEM NENHUMA GARANTIA; * sem uma garantia implícita de ADEQUAÇÃO a qualquer MERCADO ou APLICAÇÃO EM PARTICULAR. * Veja a Licença Pública Geral Menor GNU para maiores detalhes. * */ package com.liferay.portal.parsers.bbcode; import com.liferay.portal.kernel.log.Log; import com.liferay.portal.kernel.log.LogFactoryUtil; import com.liferay.portal.kernel.parsers.bbcode.BBCodeTranslator; import com.liferay.portal.kernel.util.GetterUtil; import com.liferay.portal.kernel.util.HtmlUtil; import com.liferay.portal.kernel.util.IntegerWrapper; import com.liferay.portal.kernel.util.PropsUtil; import com.liferay.portal.kernel.util.StringBundler; import com.liferay.portal.kernel.util.StringPool; import com.liferay.portal.kernel.util.StringUtil; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Stack; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * @author Iliyan Peychev */ public class HtmlBBCodeTranslatorImpl implements BBCodeTranslator { static final Pattern[] YOUTUBE_PATTERNS = new Pattern[] { Pattern.compile("https?://(?:www\\.)?youtube\\.com/watch\\?v=([A-Za-z0-9_-]+)"), Pattern.compile("https?://(?:www\\.)?youtube\\.com/embed/([A-Za-z0-9_-]+)"), Pattern.compile("https?://(?:www\\.)?youtube\\.com/v/([A-Za-z0-9_-]+)"), Pattern.compile("https?://youtu\\.be/([A-Za-z0-9_-]+)"), Pattern.compile("https?://(?:www\\.)?youtube\\.com/user/.+/([A-Za-z0-9_-]+)"), Pattern.compile("https?://(?:www\\.)?youtube\\.com/user/.+/([A-Za-z0-9_-]+)"), Pattern.compile("https?://(?:www\\.)?youtube-nocookie\\.com/v/([A-Za-z0-9_-]+)"), Pattern.compile("([A-Za-z0-9_-]{11})") // Fallback }; public HtmlBBCodeTranslatorImpl() { _listStyles = new HashMap<String, String>(); _listStyles.put("a", "list-style: lower-alpha inside;"); _listStyles.put("A", "list-style: upper-alpha inside;"); _listStyles.put("1", "list-style: decimal inside;"); _listStyles.put("i", "list-style: lower-roman inside;"); _listStyles.put("I", "list-style: upper-roman inside;"); _excludeNewLineTypes = new HashMap<String, Integer>(); _excludeNewLineTypes.put("*", BBCodeParser.TYPE_TAG_START_END); _excludeNewLineTypes.put("li", BBCodeParser.TYPE_TAG_START_END); _excludeNewLineTypes.put("table", BBCodeParser.TYPE_TAG_END); _excludeNewLineTypes.put("td", BBCodeParser.TYPE_TAG_START_END); _excludeNewLineTypes.put("th", BBCodeParser.TYPE_TAG_START_END); _excludeNewLineTypes.put("tr", BBCodeParser.TYPE_TAG_START_END); _bbCodeCharacters = new HashMap<String, String>(); _bbCodeCharacters.put("&", "&"); _bbCodeCharacters.put("<", "<"); _bbCodeCharacters.put(">", ">"); _bbCodeCharacters.put("\"", """); _bbCodeCharacters.put("'", "'"); _bbCodeCharacters.put("/", "/"); _bbCodeCharacters.put("`", "`"); _bbCodeCharacters.put("[", "["); _bbCodeCharacters.put("]", "]"); _bbCodeCharacters.put("(", "("); _bbCodeCharacters.put(")", ")"); for (int i = 0; i < _EMOTICONS.length; i++) { String[] emoticon = _EMOTICONS[i]; _emoticonDescriptions[i] = emoticon[2]; _emoticonFiles[i] = emoticon[0]; _emoticonSymbols[i] = emoticon[1]; String image = emoticon[0]; emoticon[0] = "<img alt=\"emoticon\" src=\"@theme_images_path@/emoticons/" + image + "\" >"; } } public String[] getEmoticonDescriptions() { return _emoticonDescriptions; } public String[] getEmoticonFiles() { return _emoticonFiles; } public String[][] getEmoticons() { return _EMOTICONS; } public String[] getEmoticonSymbols() { return _emoticonSymbols; } public String getHTML(String bbcode) { try { bbcode = parse(bbcode); } catch (Exception e) { _log.error("Unable to parse: " + bbcode, e); bbcode = HtmlUtil.escape(bbcode); } return bbcode; } public String parse(String text) { StringBundler sb = new StringBundler(); List<BBCodeItem> bbCodeItems = _bbCodeParser.parse(text); Stack<String> tags = new Stack<String>(); IntegerWrapper marker = new IntegerWrapper(); for (; marker.getValue() < bbCodeItems.size(); marker.increment()) { BBCodeItem bbCodeItem = bbCodeItems.get(marker.getValue()); int type = bbCodeItem.getType(); if (type == BBCodeParser.TYPE_DATA) { handleData(sb, bbCodeItems, tags, marker, bbCodeItem); } else if (type == BBCodeParser.TYPE_TAG_END) { handleTagEnd(sb, tags, bbCodeItem); } else if (type == BBCodeParser.TYPE_TAG_START) { handleTagStart(sb, bbCodeItems, tags, marker, bbCodeItem); } } return sb.toString(); } protected String escapeQuote(String quote) { StringBuilder sb = new StringBuilder(); int index = 0; Matcher matcher = _bbCodePattern.matcher(quote); Collection<String> values = _bbCodeCharacters.values(); while (matcher.find()) { String match = matcher.group(); int matchStartIndex = matcher.start(); int nextSemicolonIndex = quote.indexOf( StringPool.SEMICOLON, matchStartIndex); sb.append(quote.substring(index, matchStartIndex)); boolean entityFound = false; if (nextSemicolonIndex >= 0) { String value = quote.substring( matchStartIndex, nextSemicolonIndex + 1); if (values.contains(value)) { sb.append(value); index = matchStartIndex + value.length(); entityFound = true; } } if (!entityFound) { String escapedValue = _bbCodeCharacters.get(match); sb.append(escapedValue); index = matchStartIndex + match.length(); } } if (index < quote.length()) { sb.append(quote.substring(index, quote.length())); } return sb.toString(); } protected String extractData( List<BBCodeItem> bbCodeItems, IntegerWrapper marker, String tag, int type, boolean consume) { StringBundler sb = new StringBundler(); int index = marker.getValue() + 1; BBCodeItem bbCodeItem = null; do { bbCodeItem = bbCodeItems.get(index++); if ((bbCodeItem.getType() & type) > 0) { sb.append(bbCodeItem.getValue()); } } while ((bbCodeItem.getType() != BBCodeParser.TYPE_TAG_END) && !tag.equals(bbCodeItem.getValue())); if (consume) { marker.setValue(index - 1); } return sb.toString(); } protected void handleBold(StringBundler sb, Stack<String> tags) { handleSimpleTag(sb, tags, "strong"); } protected void handleCode( StringBundler sb, List<BBCodeItem> bbCodeItems, IntegerWrapper marker) { sb.append("<div class=\"code\">"); String code = extractData( bbCodeItems, marker, "code", BBCodeParser.TYPE_DATA, true); code = HtmlUtil.escape(code); code = code.replaceAll(StringPool.TAB, StringPool.FOUR_SPACES); String[] lines = code.split("\r?\n"); String digits = String.valueOf(lines.length + 1); for (int i = 0; i < lines.length; i++) { String index = String.valueOf(i + 1); sb.append("<span class=\"code-lines\">"); for (int j = 0; j < digits.length() - index.length(); j++) { sb.append(StringPool.NBSP); } lines[i] = StringUtil.replace( lines[i], StringPool.THREE_SPACES, "   "); lines[i] = StringUtil.replace( lines[i], StringPool.DOUBLE_SPACE, "  "); sb.append(index); sb.append("</span>"); sb.append(lines[i]); if (index.length() < lines.length) { sb.append("<br />"); } } sb.append("</div>"); } protected void handleColor( StringBundler sb, Stack<String> tags, BBCodeItem bbCodeItem) { sb.append("<span style=\"color: "); String color = bbCodeItem.getAttribute(); if (color == null) { color = "inherit"; } else { Matcher matcher = _colorPattern.matcher(color); if (!matcher.matches()) { color = "inherit"; } } sb.append(color); sb.append("\">"); tags.push("</span>"); } protected void handleData( StringBundler sb, List<BBCodeItem> bbCodeItems, Stack<String> tags, IntegerWrapper marker, BBCodeItem bbCodeItem) { String value = HtmlUtil.escape(bbCodeItem.getValue()); value = handleNewLine(bbCodeItems, tags, marker, value); for (int i = 0; i < _EMOTICONS.length; i++) { String[] emoticon = _EMOTICONS[i]; value = StringUtil.replace(value, emoticon[1], emoticon[0]); } sb.append(value); } protected void handleEmail( StringBundler sb, List<BBCodeItem> bbCodeItems, Stack<String> tags, IntegerWrapper marker, BBCodeItem bbCodeItem) { sb.append("<a href=\""); String href = bbCodeItem.getAttribute(); if (href == null) { href = extractData( bbCodeItems, marker, "email", BBCodeParser.TYPE_DATA, false); } if (!href.startsWith("mailto:")) { href = "mailto:" + href; } sb.append(HtmlUtil.escapeHREF(href)); sb.append("\">"); tags.push("</a>"); } protected void handleFontFamily( StringBundler sb, Stack<String> tags, BBCodeItem bbCodeItem) { sb.append("<span style=\"font-family: "); sb.append(HtmlUtil.escapeAttribute(bbCodeItem.getAttribute())); sb.append("\">"); tags.push("</span>"); } protected void handleFontSize( StringBundler sb, Stack<String> tags, BBCodeItem bbCodeItem) { sb.append("<span style=\"font-size: "); int size = GetterUtil.getInteger(bbCodeItem.getAttribute()); if ((size >= 1) && (size <= _fontSizes.length)) { sb.append(_fontSizes[size - 1]); } else { sb.append(_fontSizes[1]); } sb.append("px\">"); tags.push("</span>"); } protected void handleImage( StringBundler sb, List<BBCodeItem> bbCodeItems, IntegerWrapper marker) { sb.append("<img src=\""); String src = extractData( bbCodeItems, marker, "img", BBCodeParser.TYPE_DATA, true); Matcher matcher = _imagePattern.matcher(src); if (matcher.matches()) { sb.append(HtmlUtil.escapeAttribute(src)); } sb.append("\" />"); } protected void handleYoutube( StringBundler sb, List<BBCodeItem> bbCodeItems, Stack<String> tags, IntegerWrapper marker) { String videoId = extractData( bbCodeItems, marker, "youtube", BBCodeParser.TYPE_DATA, true); if (videoId.length() > 0) { int videoHeight = GetterUtil.get(PropsUtil.get("forum.video.height"), 349); int videoWidth = GetterUtil.get(PropsUtil.get("forum.video.width"), 560); // Extrai o identificador do video, parar criar o embbed for (Pattern p : YOUTUBE_PATTERNS) { Matcher m = p.matcher(videoId); if (m.find()) { sb.append("<iframe "); sb.append("width=\"").append(videoWidth).append("\" "); sb.append("height=\"").append(videoHeight).append("\" "); sb.append("src=\"http://www.youtube.com/embed/"); sb.append(HtmlUtil.escapeAttribute(m.group(1))); sb.append("\" frameborder=\"0\" allowfullscreen></iframe>"); break; } } } } protected void handleItalic(StringBundler sb, Stack<String> tags) { handleSimpleTag(sb, tags, "em"); } protected void handleList( StringBundler sb, Stack<String> tags, BBCodeItem bbCodeItem) { String listStyle = null; String tag = null; String listAttribute = bbCodeItem.getAttribute(); if (listAttribute != null) { listStyle = _listStyles.get(listAttribute); tag = "ol"; } else { tag = "ul style=\"list-style: disc inside;\""; } if (listStyle == null) { sb.append("<"); sb.append(tag); sb.append(">"); } else { sb.append("<"); sb.append(tag); sb.append(" style=\""); sb.append(listStyle); sb.append("\">"); } tags.push("</" + tag + ">"); } protected void handleListItem(StringBundler sb, Stack<String> tags) { handleSimpleTag(sb, tags, "li"); } protected String handleNewLine( List<BBCodeItem> bbCodeItems, Stack<String> tags, IntegerWrapper marker, String data) { BBCodeItem bbCodeItem = null; if ((marker.getValue() + 1) < bbCodeItems.size()) { if (data.matches("\\A\r?\n\\z")) { bbCodeItem = bbCodeItems.get(marker.getValue() + 1); if (bbCodeItem != null) { String value = bbCodeItem.getValue(); if (_excludeNewLineTypes.containsKey(value)) { int type = bbCodeItem.getType(); int excludeNewLineType = _excludeNewLineTypes.get( value); if ((type & excludeNewLineType) > 0) { data = StringPool.BLANK; } } } } else if (data.matches("(?s).*\r?\n\\z")) { bbCodeItem = bbCodeItems.get(marker.getValue() + 1); if ((bbCodeItem != null) && (bbCodeItem.getType() == BBCodeParser.TYPE_TAG_END)) { String value = bbCodeItem.getValue(); if (value.equals("*")) { data = data.substring(0, data.length() - 1); } } } } if (data.length() > 0) { data = data.replaceAll("\r?\n", "<br />"); } return data; } protected void handleQuote( StringBundler sb, Stack<String> tags, BBCodeItem bbCodeItem) { String quote = bbCodeItem.getAttribute(); if ((quote != null) && (quote.length() > 0)) { sb.append("<div class=\"quote-title\">"); sb.append(escapeQuote(quote)); sb.append(":</div>"); } sb.append("<div class=\"quote\"><div class=\"quote-content\">"); tags.push("</div></div>"); } protected void handleSimpleTag( StringBundler sb, Stack<String> tags, BBCodeItem bbCodeItem) { handleSimpleTag(sb, tags, bbCodeItem.getValue()); } protected void handleSimpleTag( StringBundler sb, Stack<String> tags, String tag) { sb.append("<"); sb.append(tag); sb.append(">"); tags.push("</" + tag + ">"); } protected void handleStrikeThrough(StringBundler sb, Stack<String> tags) { handleSimpleTag(sb, tags, "strike"); } protected void handleTable(StringBundler sb, Stack<String> tags) { handleSimpleTag(sb, tags, "table"); } protected void handleTableCell(StringBundler sb, Stack<String> tags) { handleSimpleTag(sb, tags, "td"); } protected void handleTableHeader(StringBundler sb, Stack<String> tags) { handleSimpleTag(sb, tags, "th"); } protected void handleTableRow(StringBundler sb, Stack<String> tags) { handleSimpleTag(sb, tags, "tr"); } protected void handleTagEnd( StringBundler sb, Stack<String> tags, BBCodeItem bbCodeItem) { String tag = bbCodeItem.getValue(); if (isValidTag(tag) && !tags.isEmpty()) { sb.append(tags.pop()); } } protected void handleTagStart( StringBundler sb, List<BBCodeItem> bbCodeItems, Stack<String> tags, IntegerWrapper marker, BBCodeItem bbCodeItem) { String tag = bbCodeItem.getValue(); if (!isValidTag(tag)) { return; } if (tag.equals("b")) { handleBold(sb, tags); } else if (tag.equals("center") || tag.equals("justify") || tag.equals("left") || tag.equals("right")) { handleTextAlign(sb, tags, bbCodeItem); } else if (tag.equals("code")) { handleCode(sb, bbCodeItems, marker); } else if (tag.equals("color") || tag.equals("colour")) { handleColor(sb, tags, bbCodeItem); } else if (tag.equals("email")) { handleEmail(sb, bbCodeItems, tags, marker, bbCodeItem); } else if (tag.equals("font")) { handleFontFamily(sb, tags, bbCodeItem); } else if (tag.equals("i")) { handleItalic(sb, tags); } else if (tag.equals("img")) { handleImage(sb, bbCodeItems, marker); } else if (tag.equals("li") || tag.equals("*")) { handleListItem(sb, tags); } else if (tag.equals("list")) { handleList(sb, tags, bbCodeItem); } else if (tag.equals("q") || tag.equals("quote")) { handleQuote(sb, tags, bbCodeItem); } else if (tag.equals("s")) { handleStrikeThrough(sb, tags); } else if (tag.equals("size")) { handleFontSize(sb, tags, bbCodeItem); } else if (tag.equals("table")) { handleTable(sb, tags); } else if (tag.equals("td")) { handleTableCell(sb, tags); } else if (tag.equals("th")) { handleTableHeader(sb, tags); } else if (tag.equals("tr")) { handleTableRow(sb, tags); } else if (tag.equals("url")) { handleURL(sb, bbCodeItems, tags, marker, bbCodeItem); } else if (tag.equals("youtube")) { handleYoutube(sb, bbCodeItems, tags, marker); } else { handleSimpleTag(sb, tags, bbCodeItem); } } protected void handleTextAlign( StringBundler sb, Stack<String> tags, BBCodeItem bbCodeItem) { sb.append("<p style=\"text-align: "); sb.append(bbCodeItem.getValue()); sb.append("\">"); tags.push("</p>"); } protected void handleURL( StringBundler sb, List<BBCodeItem> bbCodeItems, Stack<String> tags, IntegerWrapper marker, BBCodeItem bbCodeItem) { sb.append("<a href=\""); String href = bbCodeItem.getAttribute(); if (href == null) { href = extractData( bbCodeItems, marker, "url", BBCodeParser.TYPE_DATA, false); } Matcher matcher = _urlPattern.matcher(href); if (matcher.matches()) { sb.append(HtmlUtil.escapeHREF(href)); } sb.append("\">"); tags.push("</a>"); } protected boolean isValidTag(String tag) { if ((tag != null) && (tag.length() > 0)) { Matcher matcher = _tagPattern.matcher(tag); return matcher.matches(); } return false; } private static final String[][] _EMOTICONS = { {"happy.gif", ":)", "happy"}, {"smile.gif", ":D", "smile"}, {"cool.gif", "B)", "cool"}, {"sad.gif", ":(", "sad"}, {"tongue.gif", ":P", "tongue"}, {"laugh.gif", ":lol:", "laugh"}, {"kiss.gif", ":#", "kiss"}, {"blush.gif", ":*)", "blush"}, {"bashful.gif", ":bashful:", "bashful"}, {"smug.gif", ":smug:", "smug"}, {"blink.gif", ":blink:", "blink"}, {"huh.gif", ":huh:", "huh"}, {"mellow.gif", ":mellow:", "mellow"}, {"unsure.gif", ":unsure:", "unsure"}, {"mad.gif", ":mad:", "mad"}, {"oh_my.gif", ":O", "oh-my-goodness"}, {"roll_eyes.gif", ":rolleyes:", "roll-eyes"}, {"angry.gif", ":angry:", "angry"}, {"suspicious.gif", "8o", "suspicious"}, {"big_grin.gif", ":grin:", "grin"}, {"in_love.gif", ":love:", "in-love"}, {"bored.gif", ":bored:", "bored"}, {"closed_eyes.gif", "-_-", "closed-eyes"}, {"cold.gif", ":cold:", "cold"}, {"sleep.gif", ":sleep:", "sleep"}, {"glare.gif", ":glare:", "glare"}, {"darth_vader.gif", ":vader:", "darth-vader"}, {"dry.gif", ":dry:", "dry"}, {"exclamation.gif", ":what:", "what"}, {"girl.gif", ":girl:", "girl"}, {"karate_kid.gif", ":kid:", "karate-kid"}, {"ninja.gif", ":ph34r:", "ninja"}, {"pac_man.gif", ":V", "pac-man"}, {"wacko.gif", ":wacko:", "wacko"}, {"wink.gif", ":wink:", "wink"}, {"wub.gif", ":wub:", "wub"} }; private static Log _log = LogFactoryUtil.getLog( HtmlBBCodeTranslatorImpl.class); private Map<String, String> _bbCodeCharacters; private BBCodeParser _bbCodeParser = new BBCodeParser(); private Pattern _bbCodePattern = Pattern.compile("[]&<>'\"`\\[()]"); private Pattern _colorPattern = Pattern.compile( "^(:?aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|purple" + "|red|silver|teal|white|yellow|#(?:[0-9a-f]{3})?[0-9a-f]{3})$", Pattern.CASE_INSENSITIVE); private String[] _emoticonDescriptions = new String[_EMOTICONS.length]; private String[] _emoticonFiles = new String[_EMOTICONS.length]; private String[] _emoticonSymbols = new String[_EMOTICONS.length]; private Map<String, Integer> _excludeNewLineTypes; private int[] _fontSizes = {10, 12, 16, 18, 24, 32, 48}; private Pattern _imagePattern = Pattern.compile( "^(?:https?://|/)[-;/?:@&=+$,_.!~*'()%0-9a-z]{1,512}$", Pattern.CASE_INSENSITIVE); private Map<String, String> _listStyles; private Pattern _tagPattern = Pattern.compile( "^/?(?:b|center|code|colou?r|email|i|img|justify|left|pre|q|quote|" + "right|\\*|s|size|table|tr|th|td|li|list|font|u|url|youtube)$", Pattern.CASE_INSENSITIVE); private Pattern _urlPattern = Pattern.compile( "^[-;/?:@&=+$,_.!~*'()%0-9a-z#]{1,512}$", Pattern.CASE_INSENSITIVE); }