Extracted Replacer interface and moved replacers to a separate package.

cefexperiments
Danny van Heumen 11 years ago
parent d0c1d86f9b
commit 61a9ae412d

@ -25,6 +25,7 @@
import net.java.sip.communicator.impl.gui.*;
import net.java.sip.communicator.impl.gui.main.chat.history.*;
import net.java.sip.communicator.impl.gui.main.chat.menus.*;
import net.java.sip.communicator.impl.gui.main.chat.replacers.*;
import net.java.sip.communicator.impl.gui.utils.*;
import net.java.sip.communicator.impl.gui.utils.Constants;
import net.java.sip.communicator.plugin.desktoputil.*;
@ -55,6 +56,7 @@
* @author Yana Stamcheva
* @author Lyubomir Marinov
* @author Adam Netocny
* @author Danny van Heumen
*/
public class ChatConversationPanel
extends SIPCommScrollPane
@ -73,6 +75,12 @@ public class ChatConversationPanel
/**
* The regular expression (in the form of compiled <tt>Pattern</tt>) which
* matches URLs for the purposed of turning them into links.
*
* TODO Current pattern misses tailing '/' (slash) that is sometimes
* included in URL's. (Danny)
*
* TODO Current implementation misses # after ? has been encountered in URL.
* (Danny)
*/
private static final Pattern URL_PATTERN
= Pattern.compile(
@ -1019,10 +1027,8 @@ private void deleteAllMessagesWithoutHeader()
/**
* Formats the given message. Processes all smiley chars, new lines and
* 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!!!
* links. This method expects <u>only</u> the message's <u>body</u> to be
* provided.
*
* @param message the message to be formatted
* @param contentType the content type of the message to be formatted
@ -1038,7 +1044,7 @@ private String formatMessageAsHTML(final String original,
return "";
}
// prepare initial message source
// prepare source message
String source;
if (ChatHtmlUtils.HTML_CONTENT_TYPE.equals(contentType))
{
@ -1049,23 +1055,36 @@ private String formatMessageAsHTML(final String original,
source = StringEscapeUtils.escapeHtml4(original);
}
final Replacer[] replacers = new Replacer[]
{
return processReplacers(source,
new NewlineReplacer(),
new URLReplacer(),
new URLReplacer(URL_PATTERN),
new KeywordReplacer(keyword),
new BrTagReplacer(),
new ImgTagReplacer()
};
return processReplacers(source, replacers);
new ImgTagReplacer());
}
// FIXME decent comments
private String processReplacers(final String content, final Replacer... replacers)
/**
* Process provided replacers one by one sequentially. The output of the
* first replacer is then fed as input into the second replacer, and so on.
* <p>
* {@link Replacer}s that expect HTML content (
* {@link Replacer#expectsPlainText()}) will typically receive the complete
* message as an argument. {@linkplain Replacer}s that expect plain text
* content will typically receive small pieces that are found in between
* HTML tags. The pieces of plain text content cannot be predicted as
* results change when they are processed by other replacers.
* </p>
*
* @param content the original content to process
* @param replacers the replacers to call
* @return returns the final result message content after it has been
* processed by all replacers
*/
private String processReplacers(final String content,
final Replacer... replacers)
{
StringBuilder source = new StringBuilder(content);
for (Replacer replacer : replacers)
for (final Replacer replacer : replacers)
{
final StringBuilder target = new StringBuilder();
if (replacer.expectsPlainText())
@ -1118,236 +1137,6 @@ private String processReplacers(final String content, final Replacer... replacer
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);
}
private static final class URLReplacer
extends Replacer
{
@Override
boolean expectsPlainText()
{
return true;
}
@Override
void replace(final StringBuilder target, final String piece)
{
final Matcher m = URL_PATTERN.matcher(piece);
int prevEnd = 0;
while (m.find())
{
target.append(StringEscapeUtils.escapeHtml4(piece.substring(
prevEnd, m.start())));
prevEnd = m.end();
String url = m.group().trim();
target.append("<A href=\"");
if (url.startsWith("www"))
{
target.append("http://");
}
target.append(url);
target.append("\">");
target.append(StringEscapeUtils.escapeHtml4(url));
target.append("</A>");
}
target.append(StringEscapeUtils.escapeHtml4(piece
.substring(prevEnd)));
}
}
private static final class NewlineReplacer
extends Replacer
{
@Override
boolean expectsPlainText()
{
return false;
}
@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
extends Replacer
{
private final String keyword;
private KeywordReplacer(final String keyword)
{
this.keyword = keyword;
}
@Override
boolean expectsPlainText()
{
return true;
}
@Override
void replace(final StringBuilder target, final String piece)
{
if (this.keyword == null || this.keyword.isEmpty())
{
target.append(StringEscapeUtils.escapeHtml4(piece));
return;
}
final Matcher m =
Pattern.compile(Pattern.quote(keyword),
Pattern.CASE_INSENSITIVE).matcher(piece);
int prevEnd = 0;
while (m.find())
{
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>");
}
target.append(StringEscapeUtils.escapeHtml4(piece
.substring(prevEnd)));
}
}
private static final class BrTagReplacer extends Replacer {
@Override
boolean expectsPlainText()
{
return false;
}
@Override
void replace(final StringBuilder target, final String piece)
{
// 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));
}
}
private static final class ImgTagReplacer
extends Replacer
{
@Override
boolean expectsPlainText()
{
return false;
}
@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;
// 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 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 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.
*/
// processLinksAndHTMLChars(final String message,
// final boolean processHTMLChars,
// final String contentType)
// {
// }
/**
* Opens a link in the default browser when clicked and shows link url in a
* popup on mouseover.
@ -1644,34 +1433,6 @@ public Date getPageLastMsgTimestamp()
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;.
* The reason of this function is that the ChatPanel does not support
* &lt;br /&gt; closing tags (XHTML syntax), thus we have to remove every
* slash from each &lt;br /&gt; tags.
* @param message The source message string.
* @return The message string with properly formatted &lt;br&gt; tags.
*/
// processBrTags(String message)
// {
// }
// FIXME delete this after everything works
/**
* Formats HTML tags &lt;img ... /&gt; to &lt; img ... &gt;&lt;/img&gt; or
* &lt;IMG ... /&gt; to &lt;IMG&gt;&lt;/IMG&gt;.
* The reason of this function is that the ChatPanel does not support
* &lt;img /&gt; tags (XHTML syntax).
* Thus, we remove every slash from each &lt;img /&gt; and close it with a
* separate closing tag.
* @param message The source message string.
* @return The message string with properly formatted &lt;img&gt; tags.
*/
// processImgTags(String message)
// {
// }
/**
* Extend Editor pane to add URL tooltips.
*/

@ -0,0 +1,51 @@
/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package net.java.sip.communicator.impl.gui.main.chat;
/**
* The Replacer interface defines the expectations of message content replacers.
* Replacers get called with pieces of message content and can then perform
* appropriate modifications to the content before writing the result.
*
* @author Danny van Heumen
*/
public interface 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
*/
boolean expectsPlainText();
/**
* Actual replace operation. The replacer is called with a piece of text
* (either plain text or HTML dependening on the result of
* {@link #expectsPlainText()}) and the result should be written to
* <tt>target</tt>. If nothing gets written to <tt>target</tt> then that
* piece of text is lost, so the replacer has full control of the
* transformation of that particular piece of text.
* <p>
* If {@link #expectsPlainText()} returns true, then <tt>replace</tt> is
* called with a piece of plain text. If <tt>expectsPlainText()</tt> returns
* false, then a piece of HTML content is provided. In both cases the
* replacer is expected to write HTML content into <tt>target</tt>.
* </p>
* <p>
* <b>Note</b> that the replacer has to do <b>appropriate HTML escaping</b>
* itself when necessary, because we cannot determine the nature of the
* replacer's specific implementation.
* </p>
*
* @param target the target buffer to write the result to
* @param piece the piece of text that can be replaced
*/
void replace(StringBuilder target, String piece);
}

@ -0,0 +1,72 @@
/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package net.java.sip.communicator.impl.gui.main.chat.replacers;
import java.util.regex.*;
import net.java.sip.communicator.impl.gui.main.chat.*;
/**
* Formats HTML tags &lt;br/&gt; to &lt;br&gt; or &lt;BR/&gt; to &lt;BR&gt;.
* The reason of this {@link Replacer} is that the ChatPanel does not support
* &lt;br /&gt; closing tags (XHTML syntax), thus we have to remove every
* slash from each &lt;br /&gt; tags.
*
* @author Danny van Heumen
*/
public class BrTagReplacer
implements Replacer
{
/**
* BrTagReplacer expects HTML content.
*
* @return false for HTML content
*/
@Override
public boolean expectsPlainText()
{
return false;
}
/**
* Replace operation. "New-style" br-tags are processed and the result
* written to <tt>target</tt>.
*
* @param target destination of the replacer result
* @param piece piece of HTML content with properly formatted &lt;br&gt;
* tags.
*/
@Override
public void replace(final StringBuilder target, final String piece)
{
// 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));
}
}

@ -0,0 +1,74 @@
/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package net.java.sip.communicator.impl.gui.main.chat.replacers;
import java.util.regex.*;
import net.java.sip.communicator.impl.gui.main.chat.*;
/**
* Formats HTML tags &lt;img ... /&gt; to &lt; img ... &gt;&lt;/img&gt; or
* &lt;IMG ... /&gt; to &lt;IMG&gt;&lt;/IMG&gt;. The reason of this
* {@link Replacer} is that the ChatPanel does not support &lt;img /&gt; tags
* (XHTML syntax). Thus, we remove every slash from each &lt;img /&gt; and close
* it with a separate closing tag.
*
* @author Danny van Heumen
*/
public class ImgTagReplacer
implements Replacer
{
/**
* Img tag replacer expects HTML content.
*
* @return returns false for HTML content
*/
@Override
public boolean expectsPlainText()
{
return false;
}
/**
* Replace operation that replaces img tags that immediately close.
*
* @param target destination to write the result to
* @param piece the piece of content to process
*/
@Override
public 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;
// 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));
}
}

@ -0,0 +1,83 @@
/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package net.java.sip.communicator.impl.gui.main.chat.replacers;
import java.util.regex.*;
import net.java.sip.communicator.impl.gui.main.chat.*;
import org.apache.commons.lang3.*;
/**
* The keyword replacer used for highlighting keywords.
*
* @author Danny van Heumen
*/
public class KeywordReplacer
implements Replacer
{
/**
* The keyword to highlight.
*/
private final String keyword;
/**
* The keyword replacer with parameter for providing the keyword to
* highlight.
*
* @param keyword the keyword to highlight when replacing
*/
public KeywordReplacer(final String keyword)
{
this.keyword = keyword;
}
/**
* Type of content expected by the replacer.
*
* @return returns true for HTML content
*/
@Override
public boolean expectsPlainText()
{
return true;
}
/**
* Replace operation. Searches for the keyword in the provided piece of
* content and replaces it with the piece of content surrounded by &lt;b&gt;
* tags.
*
* @param target the destination to write the result to
* @param piece the piece of content to process
*/
@Override
public void replace(final StringBuilder target, final String piece)
{
if (this.keyword == null || this.keyword.isEmpty())
{
target.append(StringEscapeUtils.escapeHtml4(piece));
return;
}
final Matcher m =
Pattern.compile(Pattern.quote(keyword), Pattern.CASE_INSENSITIVE)
.matcher(piece);
int prevEnd = 0;
while (m.find())
{
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>");
}
target.append(StringEscapeUtils.escapeHtml4(piece.substring(prevEnd)));
}
}

@ -0,0 +1,57 @@
/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package net.java.sip.communicator.impl.gui.main.chat.replacers;
import net.java.sip.communicator.impl.gui.main.chat.*;
/**
* NewlineReplacer for replacing newlines with a combined version of a
* &lt;br&gt; tag and a <tt>\n</tt> character. This special treatment is
* necessary because copy-paste operations from the chat window do not recognize
* the &lt;br&gt; tags as line breaks.
*
* @author Danny van Heumen
*/
public class NewlineReplacer
implements Replacer
{
/**
* The NewlineReplacer expects HTML content.
*
* @return returns false
*/
@Override
public boolean expectsPlainText()
{
return false;
}
/**
* New line characters are searched and replaced with a combination of
* newline and br tag.
*
* @param target the destination for to write the replacement result to
* @param piece the piece of content to process
*/
@Override
public 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;"));
}
}

@ -0,0 +1,85 @@
/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package net.java.sip.communicator.impl.gui.main.chat.replacers;
import java.util.regex.*;
import net.java.sip.communicator.impl.gui.main.chat.*;
import org.apache.commons.lang3.*;
/**
* The URL replacer used for replacing identified URL's with variations that are
* surrounded by A-tags (anchor tags).
*
* @author Danny van Heumen
*/
public class URLReplacer
implements Replacer
{
/**
* The URL pattern to be used in matching.
*/
private final Pattern pattern;
/**
* The URL Replacer.
*
* @param urlPattern the exact URL pattern to be applied
*/
public URLReplacer(final Pattern urlPattern)
{
if (urlPattern == null)
{
throw new IllegalArgumentException("urlPattern cannot be null");
}
this.pattern = urlPattern;
}
/**
* Plain text is expected.
*
* @return returns true for plain text expectation
*/
@Override
public boolean expectsPlainText()
{
return true;
}
/**
* Replace operation for replacing URL's with a hyperlinked version.
*
* @param target destination to write the replacement result to
* @param piece the piece of content to be processed
*/
@Override
public void replace(final StringBuilder target, final String piece)
{
final Matcher m = this.pattern.matcher(piece);
int prevEnd = 0;
while (m.find())
{
target.append(StringEscapeUtils.escapeHtml4(piece.substring(
prevEnd, m.start())));
prevEnd = m.end();
String url = m.group().trim();
target.append("<A href=\"");
if (url.startsWith("www"))
{
target.append("http://");
}
target.append(url);
target.append("\">");
target.append(StringEscapeUtils.escapeHtml4(url));
target.append("</A>");
}
target.append(StringEscapeUtils.escapeHtml4(piece.substring(prevEnd)));
}
}

@ -0,0 +1,14 @@
/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
/**
* A package for replacer implementations for processing chat messages before
* they are sent to the chat conversation panel as HTML.
*/
/**
* @author Danny van Heumen
*/
package net.java.sip.communicator.impl.gui.main.chat.replacers;
Loading…
Cancel
Save