Replaced various processing methods with separate implementation.

cefexperiments
Danny van Heumen 11 years ago
parent f4481d74c4
commit aebe05fed3

@ -577,8 +577,7 @@ else if (messageType.equals(Chat.STATUS_MESSAGE))
chatString += chatString +=
GuiUtils.formatTime(date) GuiUtils.formatTime(date)
+ " " + " "
+ processLinksAndHTMLChars(contactName, true, + StringEscapeUtils.escapeHtml4(contactName) + " "
ChatHtmlUtils.TEXT_CONTENT_TYPE) + " "
+ formatMessageAsHTML(message, contentType, keyword) + formatMessageAsHTML(message, contentType, keyword)
+ endHeaderTag; + endHeaderTag;
} }
@ -589,8 +588,7 @@ else if (messageType.equals(Chat.ACTION_MESSAGE))
endHeaderTag = "</p>"; endHeaderTag = "</p>";
chatString += "* " + GuiUtils.formatTime(date) chatString += "* " + GuiUtils.formatTime(date)
+ " " + processLinksAndHTMLChars(contactName, true, + " " + StringEscapeUtils.escapeHtml4(contactName) + " "
ChatHtmlUtils.TEXT_CONTENT_TYPE) + " "
+ formatMessageAsHTML(message, contentType, keyword) + formatMessageAsHTML(message, contentType, keyword)
+ endHeaderTag; + endHeaderTag;
} }
@ -623,10 +621,8 @@ else if (messageType.equals(Chat.ERROR_MESSAGE))
if (messageTitle != null) if (messageTitle != null)
{ {
chatString += chatString +=
errorIcon errorIcon + StringEscapeUtils.escapeHtml4(messageTitle)
+ processLinksAndHTMLChars(messageTitle, true, + endHeaderTag + "<h5>"
ChatHtmlUtils.TEXT_CONTENT_TYPE) + endHeaderTag
+ "<h5>"
+ formatMessageAsHTML(message, contentType, keyword) + formatMessageAsHTML(message, contentType, keyword)
+ "</h5>"; + "</h5>";
} }
@ -1021,55 +1017,13 @@ private void deleteAllMessagesWithoutHeader()
deleteAllMessagesWithoutHeader(); deleteAllMessagesWithoutHeader();
} }
/**
* Highlights keywords searched in the history.
*
* @param message the source message
* @param contentType the content type
* @param keyword the searched keyword
* @return the formatted message
*/
private String processKeyword(final String message,
final String contentType, final String keyword)
{
if(message == null)
return message;
Matcher m
= Pattern.compile(Pattern.quote(keyword), Pattern.CASE_INSENSITIVE)
.matcher(message);
StringBuffer msgBuffer = new StringBuffer();
int prevEnd = 0;
while (m.find())
{
msgBuffer.append(StringEscapeUtils.escapeHtml4(message.substring(
prevEnd, m.start())));
prevEnd = m.end();
String keywordMatch = m.group().trim();
msgBuffer.append("<b>");
msgBuffer.append(StringEscapeUtils.escapeHtml4(keywordMatch));
msgBuffer.append("</b>");
}
/*
* If the keyword didn't match, let the outside world be able to
* discover it.
*/
if (prevEnd == 0)
return message;
msgBuffer.append(StringEscapeUtils.escapeHtml4(message
.substring(prevEnd)));
return msgBuffer.toString();
}
/** /**
* Formats the given message. Processes all smiley chars, new lines and * Formats the given message. Processes all smiley chars, new lines and
* links. * links.
* *
* TODO correctly name this method: we only expect content in either HTML or
* PLAIN TEXT format. We DON'T WANT THE HEADER STUFF OF A HTML MESSAGE!!!
*
* @param message the message to be formatted * @param message the message to be formatted
* @param contentType the content type of the message to be formatted * @param contentType the content type of the message to be formatted
* @param keyword the word to be highlighted * @param keyword the word to be highlighted
@ -1079,182 +1033,320 @@ private String formatMessageAsHTML(final String original,
final String contentType, final String contentType,
final String keyword) final String keyword)
{ {
if(original == null) if (original == null)
{
return ""; return "";
}
String message = original; // prepare initial message source
String source;
if (ChatHtmlUtils.HTML_CONTENT_TYPE.equals(contentType))
{
source = original;
}
else
{
source = StringEscapeUtils.escapeHtml4(original);
}
// If the message content type is HTML we won't process links and final Replacer[] replacers = new Replacer[]
// new lines, but only the smileys.
if (!ChatHtmlUtils.HTML_CONTENT_TYPE.equals(contentType))
{ {
new NewlineReplacer(),
new URLReplacer(),
new KeywordReplacer(keyword),
new BrTagReplacer(),
new ImgTagReplacer()
};
/* return processReplacers(source, replacers);
* We disallow HTML in plain-text messages. But processKeyword }
* introduces HTML. So we'll allow HTML if processKeyword has
* introduced it in order to not break highlighting.
*/
boolean processHTMLChars;
if ((keyword != null) && (keyword.length() != 0)) // FIXME decent comments
private String processReplacers(final String content, final Replacer... replacers)
{
StringBuilder source = new StringBuilder(content);
for (Replacer replacer : replacers)
{
final StringBuilder target = new StringBuilder();
if (replacer.expectsPlainText())
{ {
// TODO Doesn't replacing keywords first cause hyperlinks to be int startPos = 0;
// broken if the keyword is in the hyperlink? Maybe we should final Matcher plainTextInHtmlMatcher =
// insert anchors first, highlighting keywords. TEXT_TO_REPLACE_PATTERN.matcher(source);
String messageWithProcessedKeyword while (plainTextInHtmlMatcher.find())
= processKeyword(message, contentType, keyword); {
final String plainTextAsHtml =
/* plainTextInHtmlMatcher.group(1);
* The same String instance will be returned if there was no final int startMatchPosition =
* keyword match. Calling #equals() is expensive so == is plainTextInHtmlMatcher.start(1);
* intentional. final int endMatchPosition = plainTextInHtmlMatcher.end(1);
*/ target.append(source
processHTMLChars = (messageWithProcessedKeyword == message); .substring(startPos, startMatchPosition));
message = messageWithProcessedKeyword; final String plaintext =
StringEscapeUtils.unescapeHtml4(plainTextAsHtml);
// Invoke replacer.
try
{
replacer.replace(target, plaintext);
}
catch (RuntimeException e)
{
logger.error("An error occurred in replacer: "
+ replacer.getClass().getName(), e);
}
startPos = endMatchPosition;
}
target.append(source.substring(startPos));
} }
else else
processHTMLChars = true; {
// Invoke replacer.
try
{
replacer.replace(target, source.toString());
}
catch (RuntimeException e)
{
logger.error("An error occurred in replacer: "
+ replacer.getClass().getName(), e);
}
}
source = target;
}
return source.toString();
}
// TODO check all processors for correct html escaping!
// FIXME decent comments
private static abstract class Replacer
{
/**
* If a replacer expects plain text strings, then html content is
* automatically unescaped. The replacer is responsible for correctly
* escaping normal text.
*
* @return returns true if it needs plain text or false if it wants html
* content
*/
abstract boolean expectsPlainText();
// FIXME javadoc
abstract void replace(StringBuilder target, String piece);
}
message = processNewLines(processLinksAndHTMLChars( private static final class URLReplacer
message, processHTMLChars, contentType), contentType); extends Replacer
{
@Override
boolean expectsPlainText()
{
return true;
} }
// If the message content is HTML, we process br and img tags.
else @Override
void replace(final StringBuilder target, final String piece)
{ {
// For HTML message, also check for hyperlinks. final Matcher m = URL_PATTERN.matcher(piece);
int startPos = 0; int prevEnd = 0;
final StringBuilder buff = new StringBuilder();
final Matcher plainTextInHtmlMatcher = while (m.find())
TEXT_TO_REPLACE_PATTERN.matcher(message);
while (plainTextInHtmlMatcher.find())
{ {
final String plainTextAsHtml = plainTextInHtmlMatcher.group(1); target.append(StringEscapeUtils.escapeHtml4(piece.substring(
final int startMatchPosition = plainTextInHtmlMatcher.start(1); prevEnd, m.start())));
final int endMatchPosition = plainTextInHtmlMatcher.end(1); prevEnd = m.end();
if (!StringUtils.isNullOrEmpty(plainTextAsHtml)) String url = m.group().trim();
target.append("<A href=\"");
if (url.startsWith("www"))
{ {
// always add from the end of previous match, to current one target.append("http://");
// or from the start to the first match
buff.append(
message.substring(startPos, startMatchPosition));
final String plaintext =
StringEscapeUtils.unescapeHtml4(plainTextAsHtml);
buff.append(processLinksAndHTMLChars(plaintext, true,
ChatHtmlUtils.TEXT_CONTENT_TYPE));
startPos = endMatchPosition;
} }
target.append(url);
target.append("\">");
target.append(StringEscapeUtils.escapeHtml4(url));
target.append("</A>");
} }
buff.append(message.substring(startPos)); target.append(StringEscapeUtils.escapeHtml4(piece
message = buff.toString(); .substring(prevEnd)));
}
}
private static final class NewlineReplacer
extends Replacer
{
if ((keyword != null) && (keyword.length() != 0)) @Override
message = processKeyword(message, contentType, keyword); boolean expectsPlainText()
message = processImgTags(processBrTags(message)); {
return false;
} }
return message; @Override
void replace(final StringBuilder target, final String piece)
{
/*
* <br> tags are needed to visualize a new line in the html format,
* but when copied to the clipboard they are exported to the plain
* text format as ' ' and not as '\n'.
*
* See bug N4988885:
* http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4988885
*
* To fix this we need "&#10;" - the HTML-Code for ASCII-Character
* No.10 (Line feed).
*/
target.append(piece.replaceAll("\n", "<BR/>&#10;"));
}
} }
/** private static final class KeywordReplacer
* Formats all links in a given message and optionally escapes special HTML extends Replacer
* characters such as &lt;, &gt;, &amp; and &quot; in order to prevent HTML
* injection in plain-text messages such as writing
* <code>&lt;/PLAINTEXT&gt;</code>, HTML which is going to be rendered as
* such and <code>&lt;PLAINTEXT&gt;</code>. The two procedures are carried
* out in one call in order to not break URLs which contain special HTML
* characters such as &amp;.
*
* @param message The source message string.
* @param processHTMLChars <tt>true</tt> to escape the special HTML chars;
* otherwise, <tt>false</tt>
* @param contentType the message content type (html or plain text)
* @return The message string with properly formatted links.
*/
private String processLinksAndHTMLChars(final String message,
final boolean processHTMLChars,
final String contentType)
{ {
Matcher m = URL_PATTERN.matcher(message); private final String keyword;
StringBuffer msgBuffer = new StringBuffer();
int prevEnd = 0;
while (m.find()) private KeywordReplacer(final String keyword)
{
this.keyword = keyword;
}
@Override
boolean expectsPlainText()
{ {
final String rawMessage = message.substring(prevEnd, m.start()); return true;
final String fromPrevEndToStart; }
if (processHTMLChars)
@Override
void replace(final StringBuilder target, final String piece)
{
if (this.keyword == null || this.keyword.isEmpty())
{ {
fromPrevEndToStart = StringEscapeUtils.escapeHtml4(rawMessage); target.append(StringEscapeUtils.escapeHtml4(piece));
return;
} }
else
final Matcher m =
Pattern.compile(Pattern.quote(keyword),
Pattern.CASE_INSENSITIVE).matcher(piece);
int prevEnd = 0;
while (m.find())
{ {
fromPrevEndToStart = rawMessage; target.append(StringEscapeUtils.escapeHtml4(piece.substring(
prevEnd, m.start())));
prevEnd = m.end();
final String keywordMatch = m.group().trim();
target.append("<b>");
target.append(StringEscapeUtils.escapeHtml4(keywordMatch));
target.append("</b>");
} }
msgBuffer.append(fromPrevEndToStart); target.append(StringEscapeUtils.escapeHtml4(piece
prevEnd = m.end(); .substring(prevEnd)));
}
}
String url = m.group().trim(); private static final class BrTagReplacer extends Replacer {
msgBuffer.append("<A href=\""); @Override
if (url.startsWith("www")) boolean expectsPlainText()
msgBuffer.append("http://"); {
msgBuffer.append(url); return false;
msgBuffer.append("\">");
msgBuffer.append(StringEscapeUtils.escapeHtml4(url));
msgBuffer.append("</A>");
} }
final String rawMessage = message.substring(prevEnd); @Override
final String fromPrevEndToEnd; void replace(final StringBuilder target, final String piece)
if (processHTMLChars)
{ {
fromPrevEndToEnd = StringEscapeUtils.escapeHtml4(rawMessage); // Compile the regex to match something like <br .. /> or <BR .. />.
// This regex is case sensitive and keeps the style or other
// attributes of the <br> tag.
Matcher m =
Pattern.compile("<\\s*[bB][rR](.*?)(/\\s*>)").matcher(piece);
int start = 0;
// while we find some <br /> closing tags with a slash inside.
while (m.find())
{
// First, we have to copy all the message preceding the <br>
// tag.
target.append(piece.substring(start, m.start()));
// Then, we find the position of the slash inside the tag.
final int slashIndex = m.group().lastIndexOf("/");
// We copy the <br> tag till the slash exclude.
target.append(m.group().substring(0, slashIndex));
// We copy all the end of the tag following the slash exclude.
target.append(m.group().substring(slashIndex + 1));
start = m.end();
}
// Finally, we have to add the end of the message following the last
// <br> tag, or the whole message if there is no <br> tag.
target.append(piece.substring(start));
} }
else }
private static final class ImgTagReplacer
extends Replacer
{
@Override
boolean expectsPlainText()
{ {
fromPrevEndToEnd = rawMessage; return false;
} }
msgBuffer.append(fromPrevEndToEnd); @Override
void replace(final StringBuilder target, final String piece)
{
// Compile the regex to match something like <img ... /> or
// <IMG ... />. This regex is case sensitive and keeps the style,
// src or other attributes of the <img> tag.
final Pattern p = Pattern.compile("<\\s*[iI][mM][gG](.*?)(/\\s*>)");
final Matcher m = p.matcher(piece);
int slashIndex;
int start = 0;
return msgBuffer.toString(); // while we find some <img /> self-closing tags with a slash inside.
while (m.find())
{
// First, we have to copy all the message preceding the <img>
// tag.
target.append(piece.substring(start, m.start()));
// Then, we find the position of the slash inside the tag.
slashIndex = m.group().lastIndexOf("/");
// We copy the <img> tag till the slash exclude.
target.append(m.group().substring(0, slashIndex));
// We copy all the end of the tag following the slash exclude.
target.append(m.group().substring(slashIndex + 1));
// We close the tag with a separate closing tag.
target.append("</img>");
start = m.end();
}
// Finally, we have to add the end of the message following the last
// <img> tag, or the whole message if there is no <img> tag.
target.append(piece.substring(start));
}
} }
// FIXME delete this after everything works
/** /**
* Formats message new lines. * Formats all links in a given message and optionally escapes special HTML
* characters such as &lt;, &gt;, &amp; and &quot; in order to prevent HTML
* injection in plain-text messages such as writing
* <code>&lt;/PLAINTEXT&gt;</code>, HTML which is going to be rendered as
* such and <code>&lt;PLAINTEXT&gt;</code>. The two procedures are carried
* out in one call in order to not break URLs which contain special HTML
* characters such as &amp;.
* *
* @param message The source message string. * @param message The source message string.
* @param contentType message contentType (html or plain text) * @param processHTMLChars <tt>true</tt> to escape the special HTML chars;
* @return The message string with properly formatted new lines. * otherwise, <tt>false</tt>
* @param contentType the message content type (html or plain text)
* @return The message string with properly formatted links.
*/ */
private String processNewLines(String message, String contentType) // processLinksAndHTMLChars(final String message,
{ // final boolean processHTMLChars,
// final String contentType)
/* // {
* <br> tags are needed to visualize a new line in the html format, but // }
* when copied to the clipboard they are exported to the plain text
* format as ' ' and not as '\n'.
*
* See bug N4988885:
* http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4988885
*
* To fix this we need "&#10;" - the HTML-Code for ASCII-Character No.10
* (Line feed).
*/
Matcher divMatcher = DIV_PATTERN.matcher(message);
String openingTag = "";
String closingTag = "";
if (divMatcher.find())
{
openingTag = divMatcher.group(1);
message = divMatcher.group(2);
closingTag = divMatcher.group(3);
}
return openingTag + message.replaceAll("\n", "<BR/>&#10;") + closingTag;
}
/** /**
* Opens a link in the default browser when clicked and shows link url in a * Opens a link in the default browser when clicked and shows link url in a
@ -1552,6 +1644,7 @@ public Date getPageLastMsgTimestamp()
return timestamp; return timestamp;
} }
// FIXME delete this after everything works
/** /**
* Formats HTML tags &lt;br/&gt; to &lt;br&gt; or &lt;BR/&gt; to &lt;BR&gt;. * Formats HTML tags &lt;br/&gt; to &lt;br&gt; or &lt;BR/&gt; to &lt;BR&gt;.
* The reason of this function is that the ChatPanel does not support * The reason of this function is that the ChatPanel does not support
@ -1560,38 +1653,11 @@ public Date getPageLastMsgTimestamp()
* @param message The source message string. * @param message The source message string.
* @return The message string with properly formatted &lt;br&gt; tags. * @return The message string with properly formatted &lt;br&gt; tags.
*/ */
private String processBrTags(String message) // processBrTags(String message)
{ // {
// The resulting message after being processed by this function. // }
StringBuffer processedMessage = new StringBuffer();
// Compile the regex to match something like <br .. /> or <BR .. />.
// This regex is case sensitive and keeps the style or other
// attributes of the <br> tag.
Matcher m
= Pattern.compile("<\\s*[bB][rR](.*?)(/\\s*>)").matcher(message);
int start = 0;
// while we find some <br /> closing tags with a slash inside.
while(m.find())
{
// First, we have to copy all the message preceding the <br> tag.
processedMessage.append(message.substring(start, m.start()));
// Then, we find the position of the slash inside the tag.
int slash_index = m.group().lastIndexOf("/");
// We copy the <br> tag till the slash exclude.
processedMessage.append(m.group().substring(0, slash_index));
// We copy all the end of the tag following the slash exclude.
processedMessage.append(m.group().substring(slash_index+1));
start = m.end();
}
// Finally, we have to add the end of the message following the last
// <br> tag, or the whole message if there is no <br> tag.
processedMessage.append(message.substring(start));
return processedMessage.toString();
}
// FIXME delete this after everything works
/** /**
* Formats HTML tags &lt;img ... /&gt; to &lt; img ... &gt;&lt;/img&gt; or * Formats HTML tags &lt;img ... /&gt; to &lt; img ... &gt;&lt;/img&gt; or
* &lt;IMG ... /&gt; to &lt;IMG&gt;&lt;/IMG&gt;. * &lt;IMG ... /&gt; to &lt;IMG&gt;&lt;/IMG&gt;.
@ -1602,40 +1668,9 @@ private String processBrTags(String message)
* @param message The source message string. * @param message The source message string.
* @return The message string with properly formatted &lt;img&gt; tags. * @return The message string with properly formatted &lt;img&gt; tags.
*/ */
private String processImgTags(String message) // processImgTags(String message)
{ // {
// The resulting message after being processed by this function. // }
StringBuffer processedMessage = new StringBuffer();
// Compile the regex to match something like <img ... /> or
// <IMG ... />. This regex is case sensitive and keeps the style,
// src or other attributes of the <img> tag.
Pattern p = Pattern.compile("<\\s*[iI][mM][gG](.*?)(/\\s*>)");
Matcher m = p.matcher(message);
int slash_index;
int start = 0;
// while we find some <img /> self-closing tags with a slash inside.
while(m.find())
{
// First, we have to copy all the message preceding the <img> tag.
processedMessage.append(message.substring(start, m.start()));
// Then, we find the position of the slash inside the tag.
slash_index = m.group().lastIndexOf("/");
// We copy the <img> tag till the slash exclude.
processedMessage.append(m.group().substring(0, slash_index));
// We copy all the end of the tag following the slash exclude.
processedMessage.append(m.group().substring(slash_index+1));
// We close the tag with a separate closing tag.
processedMessage.append("</img>");
start = m.end();
}
// Finally, we have to add the end of the message following the last
// <img> tag, or the whole message if there is no <img> tag.
processedMessage.append(message.substring(start));
return processedMessage.toString();
}
/** /**
* Extend Editor pane to add URL tooltips. * Extend Editor pane to add URL tooltips.
@ -1748,20 +1783,6 @@ public void loadSkin()
getRightButtonMenu().loadSkin(); getRightButtonMenu().loadSkin();
} }
/**
* Highlights the string in multi user chat.
*
* @param message the message to process
* @param contentType the content type of the message
* @param keyWord the keyword to highlight
* @return the message string with the keyword highlighted
*/
public String processChatRoomHighlight(String message, String contentType,
String keyWord)
{
return processKeyword(message, contentType, keyWord);
}
/** /**
* Processes /me command in group chats. * Processes /me command in group chats.
* *

Loading…
Cancel
Save